From d0bf1ded2f165f7183f6abe17443f58b91a294f0 Mon Sep 17 00:00:00 2001 From: Bastian Date: Wed, 27 Apr 2016 16:41:00 +0200 Subject: [PATCH] * add license, tests and documentation --- Classes/AccessibleObject.php | 13 +- Classes/Controller/StandardController.php | 11 +- Classes/IterableAccessibleObject.php | 14 +- LICENSE | 695 +------------------- README.md | 133 +++- Tests/Unit/AccessibleObjectTest.php | 114 +++- Tests/Unit/Fixtures/ExampleObject.php | 12 +- Tests/Unit/Fixtures/ExampleType.php | 28 + Tests/Unit/IterableAccessibleObjectTest.php | 93 +-- Tests/Unit/TypeResolverTest.php | 35 +- composer.json | 2 + graphiql.png | Bin 0 -> 24062 bytes 12 files changed, 370 insertions(+), 780 deletions(-) create mode 100644 Tests/Unit/Fixtures/ExampleType.php create mode 100644 graphiql.png diff --git a/Classes/AccessibleObject.php b/Classes/AccessibleObject.php index 3186c5d..a217328 100644 --- a/Classes/AccessibleObject.php +++ b/Classes/AccessibleObject.php @@ -83,7 +83,7 @@ public function offsetGet($propertyName) return (boolean)call_user_func([$this->object, $propertyName]); } $result = ObjectAccess::getProperty($this->object, $propertyName); - if ($result instanceof \Iterator) { + if (is_array($result) || $result instanceof \Iterator) { return new IterableAccessibleObject($result); } if ($result instanceof \DateTimeInterface) { @@ -112,4 +112,15 @@ public function offsetUnset($offset) throw new \RuntimeException('The AccessibleObject wrapper does not allow for mutation!', 1460895625); } + /** + * This is required in order to implicitly cast wrapped string types for example + * + * @return string + */ + function __toString() + { + return (string)$this->object; + } + + } \ No newline at end of file diff --git a/Classes/Controller/StandardController.php b/Classes/Controller/StandardController.php index 8c3a8be..1b2bbd4 100644 --- a/Classes/Controller/StandardController.php +++ b/Classes/Controller/StandardController.php @@ -25,7 +25,7 @@ class StandardController extends ActionController protected $supportedMediaTypes = ['application/json', 'text/html']; /** - * @param string $endpoint + * @param string $endpoint The GraphQL endpoint, to allow for providing multiple APIs (this value is set from the routing usually) * @return void */ public function indexAction($endpoint) @@ -35,10 +35,10 @@ public function indexAction($endpoint) } /** - * @param string $endpoint - * @param string $query - * @param string $variables - * @param string $operationName + * @param string $endpoint The GraphQL endpoint, to allow for providing multiple APIs (this value is set from the routing usually) + * @param string $query The GraphQL query string (see GraphQL::execute()) + * @param string $variables JSON-encoded list of variables (if any, see GraphQL::execute()) + * @param string $operationName The operation to execute (if multiple, see GraphQL::execute()) * @return string * @Flow\SkipCsrfProtection */ @@ -47,6 +47,7 @@ public function queryAction($endpoint, $query, $variables = null, $operationName $this->verifySettings($endpoint); $decodedVariables = json_decode($variables, true); try { + $querySchema = $this->typeResolver->get($this->settings['endpoints'][$endpoint]['querySchema']); $mutationSchema = isset($this->settings['endpoints'][$endpoint]['mutationSchema']) ? $this->typeResolver->get($this->settings['endpoints'][$endpoint]['mutationSchema']) : null; $subscriptionSchema = isset($this->settings['endpoints'][$endpoint]['subscriptionSchema']) ? $this->typeResolver->get($this->settings['endpoints'][$endpoint]['subscriptionSchema']) : null; diff --git a/Classes/IterableAccessibleObject.php b/Classes/IterableAccessibleObject.php index f51704a..dfde474 100644 --- a/Classes/IterableAccessibleObject.php +++ b/Classes/IterableAccessibleObject.php @@ -58,12 +58,24 @@ public function __construct($object) } } + /** + * @return \Iterator + */ + public function getIterator() + { + return $this->innerIterator; + } + /** * @return AccessibleObject */ public function current() { - return new AccessibleObject($this->innerIterator->current()); + $currentElement = $this->innerIterator->current(); + if (is_object($currentElement)) { + return new AccessibleObject($currentElement); + } + return $currentElement; } /** diff --git a/LICENSE b/LICENSE index 30ace6a..80046e6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,21 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. \ No newline at end of file +MIT License + +Copyright (c) 2016 Bastian Waidelich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index f2274d0..f51e7b3 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,110 @@ # Wwwision.GraphQL -This package allows you to easily provide GraphQL endpoints with Neos and Flow. +Easily create GraphQL APIs with Neos and Flow. ## Background +This package is a small collection of tools that'll make it easier to provide [GraphQL](http://graphql.org/) endpoints +with Neos and Flow. +It is a wrapper for the [PHP port of webonyx](https://github.com/webonyx/graphql-php) that comes with following additions: -ExampleRootQuery.php +* A `TypeResolver` that allows for easy interdependency between complex GraphQL type definitions +* The `AccessibleObject` and `IterableAccessibleObject` wrappers that make it possible to expose arbitrary objects to + the GraphQL API +* A `StandardController` that renders the [GraphiQL IDE](https://github.com/graphql/graphiql) and acts as dispatcher + for API calls + +## Installation + +``` +composer require wwwision/graphql +``` + +(Refer to the [composer documentation](https://getcomposer.org/doc/) for more details) + +## Simple tutorial + +Create a simple Root Query definition within any Flow package: + +`ExampleRootQuery.php`: + + 'ExampleRootQuery', + 'fields' => [ + 'ping' => [ + 'type' => Type::string(), + 'resolve' => function () { + return 'pong'; + }, + ], + ], + ]); + } + } + +Now register this endpoint like so: + +`Settings.yaml`: + + Wwwision: + GraphQL: + endpoints: + 'test': + 'querySchema': 'Your\Package\ExampleRootQuery' + +And, lastly, activate the corresponding routes: + +`Settings.yaml`: + + TYPO3: + Flow: + mvc: + routes: + 'Wwwision.GraphQL': + variables: + 'endpoint': 'test' + +This will make the endpoint "test" available under `/test`. + +Note: If you already have more specific routes in place, or want to provide multiple GraphQL endpoints you can as well +activate routes in your global `Routes.yaml` file: + + - + name: 'GraphQL API' + uriPattern: '' + subRoutes: + 'GraphQLSubroutes': + package: 'Wwwision.GraphQL' + variables: + 'endpoint': 'test' + +Congratulations, your first GraphQL API is done and you should be able to invoke the GraphiQL IDE by browsing to `/test`: + +![](graphiql.png) + + +## More advanced tutorial + +Again, start with the Root Query definition + +`ExampleRootQuery.php`: ' + subRoutes: + 'GraphQLSubroutes': + package: 'Wwwision.GraphQL' + variables: + 'endpoint': 'test' + +Congratulations, your first GraphQL API is done and you should be able to invoke the GraphiQL IDE by browsing to `/test`: +![](graphiql_01.png) \ No newline at end of file diff --git a/Tests/Unit/AccessibleObjectTest.php b/Tests/Unit/AccessibleObjectTest.php index 8f87c6e..d900848 100644 --- a/Tests/Unit/AccessibleObjectTest.php +++ b/Tests/Unit/AccessibleObjectTest.php @@ -3,6 +3,7 @@ use TYPO3\Flow\Tests\UnitTestCase; use Wwwision\GraphQL\AccessibleObject; +use Wwwision\GraphQL\IterableAccessibleObject; use Wwwision\GraphQL\Tests\Unit\Fixtures\ExampleObject; require_once __DIR__ . '/Fixtures/ExampleObject.php'; @@ -10,6 +11,16 @@ class AccessibleObjectTest extends UnitTestCase { + /** + * @var AccessibleObject (wrapping an instance of ExampleObject) + */ + protected $accessibleObject; + + public function setUp() + { + $this->accessibleObject = new AccessibleObject(new ExampleObject('Foo')); + } + /** * @test */ @@ -33,55 +44,105 @@ public function simpleOffsetGetDataProvider() { return [ ['someString', 'Foo'], - ['someArray', ['string' => 'Foo', 'neos' => 'rocks']], ['isFoo', true], ['hasBar', false], - // unresolved: - ['SomeString', null], - ['somestring', null], - ['foo', null], - ['bar', null], - ['unknown', null], + + // is* and has* can be omitted (ObjectAccess behavior) + ['foo', true], + ['bar', false], + + // The following tests show that property resolving works case insensitive + // like the underlying ObjectAccess. I think it would be less error prone if they didn't + // But that's the way it currently works, so these tests merely document that behavior + ['SomeString', 'Foo'], + ['somestring', 'Foo'], + ['SoMeStRiNg', 'Foo'], ]; } /** * @test * @dataProvider simpleOffsetGetDataProvider + * @param string $propertyName + * @param mixed $expectedValue */ public function simpleOffsetGetTests($propertyName, $expectedValue) { - $accessibleObject = new AccessibleObject(new ExampleObject('Foo')); - $this->assertSame($expectedValue, $accessibleObject[$propertyName]); + $this->assertSame($expectedValue, $this->accessibleObject[$propertyName]); } /** * @test + * @expectedException \TYPO3\Flow\Reflection\Exception\PropertyNotAccessibleException */ - public function offsetWrapsArraySubObjects() + public function offsetGetThrowsExceptionForUnknownProperties() { - //TODO + $this->accessibleObject['unknown']; + } + /** + * @test + */ + public function offsetGetWrapsSimpleArrayProperties() + { + /** @var \Iterator $arrayIterator */ + $arrayIterator = $this->accessibleObject['someArray']; + $this->assertInstanceOf(IterableAccessibleObject::class, $arrayIterator); + $firstArrayValue = $arrayIterator->current(); + $firstArrayKey = $arrayIterator->key(); + $arrayIterator->next(); + $secondArrayValue = $arrayIterator->current(); + $secondArrayKey = $arrayIterator->key(); + $this->assertSame('string', $firstArrayKey); + $this->assertSame('neos', $secondArrayKey); + $this->assertSame('Foo', $firstArrayValue); + $this->assertSame('rocks', $secondArrayValue); } /** * @test */ - public function offsetWrapsIterableSubObjects() + public function offsetGetReturnsDateTimeProperties() { - //TODO + /** @var \DateTimeInterface $date */ + $date = $this->accessibleObject['someDate']; + $this->assertInstanceOf(\DateTimeInterface::class, $date); + $this->assertSame('13.12.1980', $date->format('d.m.Y')); + } + /** + * @test + */ + public function offsetGetWrapsArraySubObjects() + { + /** @var \Iterator $subObjectsIterator */ + $subObjectsIterator = $this->accessibleObject['someSubObjectsArray']; + $this->assertInstanceOf(IterableAccessibleObject::class, $subObjectsIterator); + $firstSubObject = $subObjectsIterator->current(); + $subObjectsIterator->next(); + $secondSubObject = $subObjectsIterator->current(); + $this->assertInstanceOf(AccessibleObject::class, $firstSubObject); + $this->assertInstanceOf(AccessibleObject::class, $secondSubObject); + $this->assertSame('Foo nested a', $firstSubObject['someString']); + $this->assertSame('Foo nested b', $secondSubObject['someString']); } /** * @test */ - public function offsetGetReturnsDateTimeProperties() + public function offsetGetWrapsIterableSubObjects() { - $accessibleObject = new AccessibleObject(new ExampleObject('Foo')); - #$this->assertInstanceOf - $this->assertSame('13.12.1980', $accessibleObject['someDate']->format('d.m.Y')); + /** @var \Iterator $subObjectsIterator */ + $subObjectsIterator = $this->accessibleObject['someSubObjectsIterator']; + $this->assertInstanceOf(IterableAccessibleObject::class, $subObjectsIterator); + $firstSubObject = $subObjectsIterator->current(); + $subObjectsIterator->next(); + $secondSubObject = $subObjectsIterator->current(); + $this->assertInstanceOf(AccessibleObject::class, $firstSubObject); + $this->assertInstanceOf(AccessibleObject::class, $secondSubObject); + $this->assertSame('Foo nested a', $firstSubObject['someString']); + $this->assertSame('Foo nested b', $secondSubObject['someString']); } /** @@ -89,8 +150,9 @@ public function offsetGetReturnsDateTimeProperties() */ public function offsetGetWrapsSubObjects() { - $accessibleObject = new AccessibleObject(new ExampleObject('Foo')); - $this->assertSame('Foo nested nested', $accessibleObject['someSubObject']['someSubObject']['someString']); + $this->assertInstanceOf(AccessibleObject::class, $this->accessibleObject['someSubObject']); + $this->assertInstanceOf(AccessibleObject::class, $this->accessibleObject['someSubObject']['someSubObject']); + $this->assertSame('Foo nested nested', $this->accessibleObject['someSubObject']['someSubObject']['someString']); } /** @@ -99,8 +161,7 @@ public function offsetGetWrapsSubObjects() */ public function offsetSetThrowsException() { - $accessibleObject = new AccessibleObject(new ExampleObject('Foo')); - $accessibleObject['someString'] = 'This must not be possible'; + $this->accessibleObject['someString'] = 'This must not be possible'; } /** @@ -109,7 +170,14 @@ public function offsetSetThrowsException() */ public function offsetUnsetThrowsException() { - $accessibleObject = new AccessibleObject(new ExampleObject('Foo')); - unset($accessibleObject['someString']); + unset($this->accessibleObject['someString']); + } + + /** + * @test + */ + public function toStringReturnsCastedObject() + { + $this->assertSame('ExampleObject (string-casted)', (string)$this->accessibleObject); } } \ No newline at end of file diff --git a/Tests/Unit/Fixtures/ExampleObject.php b/Tests/Unit/Fixtures/ExampleObject.php index 4156fa5..4406036 100644 --- a/Tests/Unit/Fixtures/ExampleObject.php +++ b/Tests/Unit/Fixtures/ExampleObject.php @@ -1,16 +1,17 @@ string = $string; } @@ -57,4 +58,9 @@ public function getSomeSubObjectsIterator() { return new \ArrayIterator($this->getSomeSubObjectsArray()); } + + public function __toString() + { + return 'ExampleObject (string-casted)'; + } } \ No newline at end of file diff --git a/Tests/Unit/Fixtures/ExampleType.php b/Tests/Unit/Fixtures/ExampleType.php new file mode 100644 index 0000000..f6029a0 --- /dev/null +++ b/Tests/Unit/Fixtures/ExampleType.php @@ -0,0 +1,28 @@ + 'ExampleType', + 'fields' => [ + 'someString' => ['type' => Type::string()], + 'selfReference' => ['type' => $typeResolver->get(ExampleType::class)], + ], + ]); + } + +} \ No newline at end of file diff --git a/Tests/Unit/IterableAccessibleObjectTest.php b/Tests/Unit/IterableAccessibleObjectTest.php index 469c677..305b12e 100644 --- a/Tests/Unit/IterableAccessibleObjectTest.php +++ b/Tests/Unit/IterableAccessibleObjectTest.php @@ -3,6 +3,7 @@ use TYPO3\Flow\Tests\UnitTestCase; use Wwwision\GraphQL\AccessibleObject; +use Wwwision\GraphQL\IterableAccessibleObject; use Wwwision\GraphQL\Tests\Unit\Fixtures\ExampleObject; require_once __DIR__ . '/Fixtures/ExampleObject.php'; @@ -11,105 +12,75 @@ class IterableAccessibleObjectTest extends UnitTestCase { /** - * @test + * @var IterableAccessibleObject (wrapping instances of ExampleObject) */ - public function getObjectReturnsTheUnalteredObject() - { - $object = new \stdClass(); - $accessibleObject = new AccessibleObject($object); - $this->assertSame($object, $accessibleObject->getObject()); - } - - /** - * @test - */ - public function offsetExistsReturnsFalseIfObjectIsNotSet() - { - $accessibleObject = new AccessibleObject(null); - $this->assertFalse($accessibleObject->offsetExists('foo')); - } + protected $iterableAccessibleObject; - public function simpleOffsetGetDataProvider() + public function setUp() { - return [ - ['someString', 'Foo'], - ['someArray', ['string' => 'Foo', 'neos' => 'rocks']], - ['isFoo', true], - ['hasBar', false], - - // unresolved: - ['SomeString', null], - ['somestring', null], - ['foo', null], - ['bar', null], - ['unknown', null], - ]; + $this->iterableAccessibleObject = new IterableAccessibleObject([ + new ExampleObject('Foo'), + new ExampleObject('Bar') + ]); } /** * @test - * @dataProvider simpleOffsetGetDataProvider + * @expectedException \InvalidArgumentException */ - public function simpleOffsetGetTests($propertyName, $expectedValue) + public function constructorThrowsExceptionWhenPassedNull() { - $accessibleObject = new AccessibleObject(new ExampleObject('Foo')); - $this->assertSame($expectedValue, $accessibleObject[$propertyName]); + new IterableAccessibleObject(null); } /** * @test + * @expectedException \InvalidArgumentException */ - public function offsetWrapsArraySubObjects() + public function constructorThrowsExceptionWhenPassedNonIterableObject() { - //TODO - + /** @noinspection PhpParamsInspection */ + new IterableAccessibleObject(new \stdClass()); } /** * @test */ - public function offsetWrapsIterableSubObjects() + public function constructorConvertsArraysToArrayIterator() { - //TODO - + $someArray = ['foo' => 'Foo', 'bar' => 'Bar']; + $iterableAccessibleObject = new IterableAccessibleObject($someArray); + $this->assertInstanceOf(\ArrayIterator::class, $iterableAccessibleObject->getIterator()); + $this->assertSame($someArray, iterator_to_array($iterableAccessibleObject->getIterator())); } /** * @test */ - public function offsetGetReturnsDateTimeProperties() + public function getIteratorReturnsTheUnalteredInnerIterator() { - $accessibleObject = new AccessibleObject(new ExampleObject('Foo')); - #$this->assertInstanceOf - $this->assertSame('13.12.1980', $accessibleObject['someDate']->format('d.m.Y')); + $someIterator = new \ArrayIterator(['foo' => 'Foo', 'bar' => 'Bar']); + $iterableAccessibleObject = new IterableAccessibleObject($someIterator); + $this->assertSame($someIterator, $iterableAccessibleObject->getIterator()); } /** * @test */ - public function offsetGetWrapsSubObjects() + public function currentObjectElementsAreWrapped() { - $accessibleObject = new AccessibleObject(new ExampleObject('Foo')); - $this->assertSame('Foo nested nested', $accessibleObject['someSubObject']['someSubObject']['someString']); + $this->assertInstanceOf(AccessibleObject::class, $this->iterableAccessibleObject->current()); + $this->assertSame('Foo', $this->iterableAccessibleObject->current()['someString']); } - /** + /** * @test - * @expectedException \RuntimeException */ - public function offsetSetThrowsException() + public function currentScalarElementsAreNotWrapped() { - $accessibleObject = new AccessibleObject(new ExampleObject('Foo')); - $accessibleObject['someString'] = 'This must not be possible'; - } + $arrayProperty = ['foo' => 'Foo', 'bar' => 'Bar']; + $iterableAccessibleObject = new IterableAccessibleObject([$arrayProperty]); - /** - * @test - * @expectedException \RuntimeException - */ - public function offsetUnsetThrowsException() - { - $accessibleObject = new AccessibleObject(new ExampleObject('Foo')); - unset($accessibleObject['someString']); + $this->assertSame($arrayProperty, $iterableAccessibleObject->current()); } } \ No newline at end of file diff --git a/Tests/Unit/TypeResolverTest.php b/Tests/Unit/TypeResolverTest.php index 50a4842..f50e2b6 100644 --- a/Tests/Unit/TypeResolverTest.php +++ b/Tests/Unit/TypeResolverTest.php @@ -2,19 +2,29 @@ namespace Wwwision\GraphQL\Tests\Unit; use TYPO3\Flow\Tests\UnitTestCase; -use Wwwision\GraphQL\AccessibleObject; +use Wwwision\GraphQL\Tests\Unit\Fixtures\ExampleType; use Wwwision\GraphQL\TypeResolver; class TypeResolverTest extends UnitTestCase { + /** + * @var TypeResolver + */ + protected $typeResolver; + + public function setUp() + { + $this->typeResolver = new TypeResolver(); + } + /** * @test * @expectedException \InvalidArgumentException */ public function getThrowsExceptionIfTypeClassNameIsNoString() { - new TypeResolver(123); + $this->typeResolver->get(123); } /** @@ -23,6 +33,25 @@ public function getThrowsExceptionIfTypeClassNameIsNoString() */ public function getThrowsExceptionIfTypeClassNameIsNoValidTypeDefinition() { - new TypeResolver(new \stdClass()); + $this->typeResolver->get('stdClass'); + } + + /** + * @test + */ + public function getSupportsRecursiveTypes() + { + $exampleType = $this->typeResolver->get(ExampleType::class); + $this->assertSame('ExampleType', $exampleType->name); + } + + /** + * @test + */ + public function getReturnsTheSameInstancePerType() + { + $exampleType1 = $this->typeResolver->get(ExampleType::class); + $exampleType2 = $this->typeResolver->get(ExampleType::class); + $this->assertSame($exampleType1, $exampleType2); } } \ No newline at end of file diff --git a/composer.json b/composer.json index 01bfea2..38133ea 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,9 @@ "description": "Base package to create GraphQL endpoints with Flow", "type": "neos-package", "name": "wwwision/graphql", + "license": "MIT", "require": { + "typo3/flow": "~3.0", "webonyx/graphql-php": "~0.6" }, "autoload": { diff --git a/graphiql.png b/graphiql.png new file mode 100644 index 0000000000000000000000000000000000000000..f4c3caf74497479a84a81a375987f2d7fee672ba GIT binary patch literal 24062 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 K*)K8{f&T-+6qZB) literal 0 HcmV?d00001