|
| 1 | +# Doctrine-faced RPC Client |
| 2 | + |
| 3 | +## Usage |
| 4 | + |
| 5 | +```php |
| 6 | +class \MyVendor\Api\Entity\MyEntity { |
| 7 | + /** @var string */ |
| 8 | + private $id; |
| 9 | + /** @var string */ |
| 10 | + private $payload; |
| 11 | + |
| 12 | + public function getId() { return $this->id; } |
| 13 | + |
| 14 | + public function getPayload() { return $this->payload; } |
| 15 | +} |
| 16 | + |
| 17 | +``` |
| 18 | + |
| 19 | +`Resources/config/api/MyEntity.api.yml` content: |
| 20 | + |
| 21 | +```yml |
| 22 | +MyVendor\Api\Entity\MyEntity: |
| 23 | + type: entity |
| 24 | + id: |
| 25 | + id: |
| 26 | + type: int |
| 27 | + |
| 28 | + fields: |
| 29 | + payload: |
| 30 | + type: string |
| 31 | + |
| 32 | + client: |
| 33 | + name: my-client |
| 34 | + entityPath: my-entity |
| 35 | +``` |
| 36 | +
|
| 37 | +Configure `EntityManager` |
| 38 | +```php |
| 39 | +class RpcClient implements RpcClientInterface { |
| 40 | + /** RpcClient impl */ |
| 41 | +} |
| 42 | +
|
| 43 | +$client = new RpcClient(); |
| 44 | +
|
| 45 | +$registry = new ClientRegistry(); |
| 46 | +$registry->add('my-client', $client); |
| 47 | +
|
| 48 | +$configuration = new Configuration(); |
| 49 | +$configuration->setMetadataFactory(new EntityMetadataFactory()); |
| 50 | +$configuration->setRegistry($this->registry); |
| 51 | +$configuration->setEntityFactoryClass(ProxyFactory::class); |
| 52 | +$configuration->setProxyDir(CACHE_DIR . '/doctrine/proxy/'); |
| 53 | +$configuration->setProxyNamespace('MyVendor\Api\Proxy'); |
| 54 | +$driver = new MappingDriverChain(); |
| 55 | +$driver->addDriver( |
| 56 | + new YmlMetadataDriver( |
| 57 | + new SymfonyFileLocator( |
| 58 | + [ |
| 59 | + __DIR__ . '/../Resources/config/api/' => 'MyVendor\Api\Entity', |
| 60 | + ], |
| 61 | + '.api.yml', |
| 62 | + DIRECTORY_SEPARATOR) |
| 63 | + ), |
| 64 | + 'MyVendor\Api\Entity' |
| 65 | +); |
| 66 | +$configuration->setDriver($driver); |
| 67 | +
|
| 68 | +$manager = new EntityManager($configuration); |
| 69 | +``` |
| 70 | + |
| 71 | + |
| 72 | +Call entity-manager to retrieve entities through your api |
| 73 | +```php |
| 74 | +$samples = $manager->getRepository(\MyVendor\Api\Entity\MyEntity::class)->findBy(['payload'=>'sample']); |
| 75 | +foreach ($samples as $sample) { |
| 76 | + var_dump($sample->getId()); |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +## References |
| 81 | + |
| 82 | +You could reference other API entities via relation annotations. General bi-directional self-reference relation below: |
| 83 | + |
| 84 | +```yml |
| 85 | +MyVendor\Api\Entity\MyEntity: |
| 86 | + type: entity |
| 87 | + id: |
| 88 | + id: |
| 89 | + type: int |
| 90 | +
|
| 91 | + fields: |
| 92 | + payload: |
| 93 | + type: string |
| 94 | + |
| 95 | + manyToOne: |
| 96 | + parent: |
| 97 | + target: MyVendor\Api\Entity\MyEntity |
| 98 | + inversedBy: children |
| 99 | + oneToMany: |
| 100 | + children: |
| 101 | + target: MyVendor\Api\Entity\MyEntity |
| 102 | + mappedBy: parent |
| 103 | +``` |
| 104 | + |
| 105 | +In order to make `*toMany` relations works flawlessly you should define the mapped class property |
| 106 | +as `Entity[]|ArrayCollection` as hydrator will substitute your relation property with lazy-loading collection interface. |
| 107 | + |
| 108 | +### Note on lazy-loading |
| 109 | + |
| 110 | +Generic API is not a DB, so eager reference pre-fetching will always result in additional API query, |
| 111 | +so current implementation always assumes that all requests are extra-lazy. |
| 112 | +This means that no data will be fetched until you really need it, and you'll have only lazy proxy object before that happens. |
| 113 | + |
| 114 | +Keep it in the mind |
| 115 | + |
| 116 | +## Hacking into fetching process |
| 117 | + |
| 118 | +### Custom repository |
| 119 | + |
| 120 | +Just call defined RpcClient from your repository |
| 121 | + |
| 122 | +```php |
| 123 | +class MyRepository extends \Bankiru\Api\Doctrine\EntityRepository { |
| 124 | + public function callCustomRpcMethod() |
| 125 | + { |
| 126 | + $request = new \Bankiru\Api\Rpc\RpcRequest('my-method',['param1'=>'value1']); |
| 127 | + $data = $this->getClient()->invoke([$request])->getResponse($request); |
| 128 | + |
| 129 | + return $data; |
| 130 | + } |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +Or more portable with mapping |
| 135 | + |
| 136 | +```yml |
| 137 | +MyVendor\Api\Entity\MyEntity: |
| 138 | + type: entity |
| 139 | + id: |
| 140 | + id: |
| 141 | + type: int |
| 142 | +
|
| 143 | + fields: |
| 144 | + payload: |
| 145 | + type: string |
| 146 | + |
| 147 | + repositoryClass: MyVendor\Api\Repository\MyRepository # This will override repository for MyEntity |
| 148 | + client: |
| 149 | + name: my-client |
| 150 | + # entityPath: my-entity autoconfigures find and search methods for you as following, but it is not overridable |
| 151 | + # You can also specify path separator as |
| 152 | + # entityPathSeparator: "-" |
| 153 | + # To make autogenerated methods look like my-entity-find |
| 154 | + methods: |
| 155 | + find: my-entity\find # find method is mandatory to find calls work |
| 156 | + search: my-entity\search # find method is mandatory to findBy calls work |
| 157 | + custom: my-custom-method # do some custom stuff |
| 158 | +``` |
| 159 | + |
| 160 | +```php |
| 161 | +class MyRepository extends \Bankiru\Api\Doctrine\EntityRepository { |
| 162 | + public function callCustomRpcMethod() |
| 163 | + { |
| 164 | + $request = new \Bankiru\Api\Rpc\RpcRequest( |
| 165 | + $this->getMetadata()->getMethodContainer()->getMethod('custom'), |
| 166 | + ['param1'=>'value1'] |
| 167 | + ); |
| 168 | + $data = $this->getClient()->invoke([$request])->getResponse($request); |
| 169 | + |
| 170 | + return $data; |
| 171 | + } |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +### Custom searcher |
| 176 | + |
| 177 | +`Searcher` and `SearchTransformer` are the interfaces classes that converts doctrine-faced `findBy` arguments (criteria, order, limit, offset) |
| 178 | +to your custom api query set. This library provides you with the default `DoctrineNativeSearcher` class which translated these arguments to plain criteria array. |
| 179 | + |
| 180 | +You can override searcher via `.api.yml` mapping file: |
| 181 | + |
| 182 | +```yml |
| 183 | +MyVendor\Api\Entity\MyEntity: |
| 184 | + client: |
| 185 | + searcher: Bankiru\Api\Rpc\DoctrineNativeSearcher # The defaults |
| 186 | +``` |
| 187 | + |
| 188 | +`Searcher` is required to implement the respective `Bankiru\Api\Doctrine\Rpc\Searcher` interface. |
| 189 | + |
| 190 | +### Custom hydrator |
| 191 | + |
| 192 | +`Hydrator` is the object factory which takes the plain ('dehydrated') data and converts it to rich ('hydrated') object with collections, relationship etc. |
| 193 | +Current Hydrator accepts only plain-list parametrized objects. |
| 194 | + |
| 195 | +### Custom field types |
| 196 | + |
| 197 | +You could register additional field types to configuration `TypeRegistry` (`Configuration::getTypeRegistry()`). Just implement the `Type` and register it via `TypeRegistry::add('alias', $type)` |
| 198 | + |
| 199 | +### TBD |
| 200 | + |
| 201 | +* No embeddables |
| 202 | +* No relation inheritance |
| 203 | + |
| 204 | +### Todo |
| 205 | + |
| 206 | +* Bad `Finder`|`Searcher`|`Counter` instantiation |
| 207 | +* No cache invalidation |
| 208 | +* |
0 commit comments