I am learning OpenApi/Swagger API with Api-Platform. I created a new endpoint, that accepts values of an enum as a parameter:
#[ApiResource(
itemOperations: [
'get_by_name' => [
'openapi_context' => [
....
'parameters' => [
[
'in' => 'header',
'name' => 'X-Server-Region',
'schema' => [
'type' => 'string',
'enum' => ['server1', 'server2'],
'example' => 'server1',
],
'description' => 'Server to select',
'required' => true
],
...
)]
However, this is a rather common param and values can be updated frequently (as more servers are added), I'd like to use some kind of template.
So I tried:
<?php
namespace App\Enum;
enum Server: string
{
case SERVER1 = 'server1';
case SERVER2 = 'server2';
...
}
with
'enum' => [...Server::cases()],
or
'enum' => [Server::class],
and many other forms of that, to no avail.
I tried to understand the concept of components, but could not find a way to use them in Symfony/Api Platform.
How could I reuse an enum at different endpoints?
Enums being fairly new to PHP, they are not yet directly supported by Api-Platform.
Support will come, but for the time being you'll have to explicitly list each of the cases manually on the configuration.
While you could also store the list of 'cases' in a constant in a class (you could even do it in the enum itself):
enum Server : string {
const CASES = ['server1', 'server2']
case SERVER1 = 'server1';
case SERVER2 = 'server2';
}
And then use that constant directly in annotations or attributes:
parameters' => [
[
'in' => 'header',
'name' => 'X-Server-Region',
'schema' => [
'type' => 'string',
'enum' => Server::CASES,
'example' => 'server1',
],
'description' => 'Server to select',
'required' => true
],
... this wouldn't be really using the enum advantages, as you would still need to edit the cases in two places instead of only just the one; and would only be convenient for annotations or attributes. If you used XML or YAML configuration for your API resources, it wouldn't be that great.
To temporarily solve the problem until the Enum support is available, you can create a class with a prototype of your data to reuse it in your API. This way you can reuse the same data set without duplicating it. This will make things easier for you in the future.
And so you create a prototype of your data like this:
class Server
{
public const PROTOTYPE = [ 'select1', 'select2', 'select3'];
}
Import calss and then call it in your annotations like this:
'enum' => Server::PROTOTYPE,
Related
I have a setup with Country and Currency objects. A Country may have 0 or more Currency objects and vice versa there's the Currency that may be used by 0 or more Country objects. The problem is the data is not returned to the front-end (api requests).
In Country Entity
/**
* #var Collection|ArrayCollection|Currency[]
* #ORM\ManyToMany(targetEntity="Currency", inversedBy="countries", fetch="EAGER")
* #ORM\JoinTable(
* name="country_country_currencies",
* joinColumns={#ORM\JoinColumn(name="country_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="currency_id", referencedColumnName="id")}
* )
*/
protected $currencies;
In Currency Entity
/**
* #var Collection|ArrayCollection|Currency[]
* #ORM\ManyToMany(targetEntity="Country", mappedBy="currencies", fetch="EAGER")
*/
protected $countries;
Both sides have a __construct() function setting the initial values to new ArrayCollection(). Both have their own get*(), add*() and remove*() functions.
When debugging the DoctrineResource::fetch() function on a single object (e.g. /currencies/45) the $entity object does contain the data when executing return $entity (last line of fethc()), so I know the data is there.
However, when it finally returns to the "front-end", I'm missing the data of the ManyToMany relation:
As you can see above: countries is empty.
When requesting from the other side (from Country), the currencies is empty.
Additional info
Other similar questions had suggestions in comments or answers I'll put here straight away as I already checked those.
Doctrine caching - I ran the following commands:
./vendor/bin/doctrine-module orm:clear-cache:query
./vendor/bin/doctrine-module orm:clear-cache:result
./vendor/bin/doctrine-module orm:clear-cache:metadata
I've made sure that Zend Framework caching is disabled (also, development mode is enabled via composer development-enable).
The Entity proxies generated have also been deleted multiple times to make sure they aren't the issue. I've tried also without fetch="EAGER" (without is the original actually), but that yields the same result.
Using wrong hydrator for Entities: both are configured to use the Doctrine EntityManager (same for both):
'zf-hal' => [
'metadata_map' => [
\Country::class => [
'route_identifier_name' => 'id',
'entity_identifier_name' => 'id',
'route_name' => 'country.rest.doctrine.country',
'hydrator' => 'Country\\V1\\Rest\\Country\\CountryHydrator',
'max_depth' => 3,
],
\Country\V1\Rest\Country\CountryCollection::class => [
'entity_identifier_name' => 'id',
'route_name' => 'country.rest.doctrine.country',
'is_collection' => true,
],
// other entity
],
],
// some more config
'zf-apigility' => [
'doctrine-connected' => [
\Country\V1\Rest\Country\CountryResource::class => [
'object_manager' => 'doctrine.entitymanager.orm_default',
'hydrator' => 'Country\\V1\\Rest\\Country\\CountryHydrator',
],
// other entity
],
],
'doctrine-hydrator' => [
'Country\\V1\\Rest\\Country\\CountryHydrator' => [
'entity_class' => \Salesupply\Core\Country\Entity\Country::class,
'object_manager' => 'doctrine.entitymanager.orm_default',
'by_value' => true,
'strategies' => [],
'use_generated_hydrator' => true,
],
// other entity
],
Another suggested the content negotiation type might be set to json instead of HalJson: both (everything really) is set to HalJson:
'zf-content-negotiation' => [
'controllers' => [
'Country\\V1\\Rest\\Country\\Controller' => 'HalJson',
'Country\\V1\\Rest\\Currency\\Controller' => 'HalJson',
],
],
The ApiSkeletons vendor package has this by design. I've opened an issue on Github some months back.
To ensure you receive back collections:
Create a strategy class and extend the AllowRemoveByValue strategy of Doctrine.
Overwrite the extract function to return a Collection, either filled or empty
That's it.
Full class:
namespace Application\Strategy;
use DoctrineModule\Stdlib\Hydrator\Strategy\AllowRemoveByValue;
use ZF\Hal\Collection;
class UniDirectionalToManyStrategy extends AllowRemoveByValue
{
public function extract($value)
{
return new Collection($value ?: []);
}
}
Apply this strategy where you need it. E.g. your Advert as many Applications, so the config should be modified like so:
'doctrine-hydrator' => [
'Application\\V1\\Entity\\ApplicationHydrator' => [
// other config
'strategies' => [
'relatedToManyPropertyName' => \Application\Strategy\UniDirectionalToManyStrategy::class,
],
],
],
Now collections should be returned.
Quick note: this will only work as a strategy for the *ToMany side.
I m using a basic template for a small project on Yii2. I have already set the module Language Picker of Lajax (Doc) and I am trying now to manage the translation with the module Translate Manager of Lajax (Github). The plugin is scanning perfectly the project and getting the translatable texts. I even set some translations through this module and everything is saved in the database, but these translations are not set when changing the language.
here are my web.php Configurations:
'language' => 'en-GB',
'components' => [
...
'languagepicker' => [
'class' => 'lajax\languagepicker\Component',
'languages' => ['en-GB', 'fr-FR']
],
'i18n' => [
'translations' => [
'*' => [
'class' => 'yii\i18n\DbMessageSource',
'db' => 'db',
'sourceLanguage' => 'en-GB',
'sourceMessageTable' => '{{%language_source}}',
'messageTable' => '{{%language_translate}}',
'forceTranslation' => true,
'cachingDuration' => 86400,
'enableCaching' => true,
],
],
],
...
]
'modules' => [
...
'translatemanager' => [
'class' => 'lajax\translatemanager\Module',
'root' => '#app', // The root directory of the project scan.
'scanRootParentDirectory' => false, // Whether scan the defined `root` parent directory, or the folder itself.
// IMPORTANT: for detailed instructions read the chapter about root configuration.
'layout' => 'language', // Name of the used layout. If using own layout use 'null'.
'allowedIPs' => ['127.0.0.1'], // IP addresses from which the translation interface is accessible.
'roles' => ['#'], // For setting access levels to the translating interface.
'tmpDir' => '#runtime', // Writable directory for the client-side temporary language files.
// IMPORTANT: must be identical for all applications (the AssetsManager serves the JavaScript files containing language elements from this directory).
'phpTranslators' => ['::t'], // list of the php function for translating messages.
'jsTranslators' => ['lajax.t'], // list of the js function for translating messages.
'patterns' => ['*.js', '*.php'],// list of file extensions that contain language elements.
'ignoredCategories' => ['yii'], // these categories won't be included in the language database.
'ignoredItems' => ['config'], // these files will not be processed.
'scanTimeLimit' => null, // increase to prevent "Maximum execution time" errors, if null the default max_execution_time will be used
'searchEmptyCommand' => '!', // the search string to enter in the 'Translation' search field to find not yet translated items, set to null to disable this feature
'defaultExportStatus' => 1, // the default selection of languages to export, set to 0 to select all languages by default
'defaultExportFormat' => 'json',// the default format for export, can be 'json' or 'xml'
'tables' => [ // Properties of individual tables
[
'connection' => 'db', // connection identifier
'table' => '{{%language}}', // table name
'columns' => ['name', 'name_ascii'],// names of multilingual fields
'category' => 'database-table-name',// the category is the database table name
]
],
'scanners' => [ // define this if you need to override default scanners (below)
'\lajax\translatemanager\services\scanners\ScannerPhpFunction',
'\lajax\translatemanager\services\scanners\ScannerPhpArray',
'\lajax\translatemanager\services\scanners\ScannerJavaScriptFunction',
'\lajax\translatemanager\services\scanners\ScannerDatabase',
],
],
...
]
I always use something like this im code for translatable strings:
<?= Yii::t('app','Test') ?>
Am I doing something wrong?
I was wondering if there is a library that implements a SQL-like interface to access data in array, e.g.
Input:
[
['name' => 'Tom', 'age' => 27, 'location' => ['country' => 'GB']],
['name' => 'Jerry', 'age' => 16, 'location' => ['country' => 'LT']],
['name' => 'Stuart', 'age' => 26, 'location' => ['country' => 'GB']]
]
Fictional query:
SELECT name, location.country FROM {input} WHERE age > 18 ORDER BY age DESC
Which would produce a variation of:
[
['name' => 'Tom', 'location.country' => 'GB'],
['name' => 'Tom', 'location.country' => 'GB']
]
Note, I am perfectly aware of array_filter and alike implementations that I could put together on the spot. I am looking for query like interface to access data.
You can use LINQ, with PHP implementations such as phpLinq or LINQ for PHP or plinq
I found the following to be the closest of all available alternatives:
https://github.com/braincrafted/arrayquery
ArrayQuery does not provide query language, though it does provide query API, e.g.
$thorinsCompany = [
[ 'name' => 'Bilbo Baggins', 'race' => 'Hobbit' ],
[ 'name' => 'Gandalf', 'race' => 'Wizard' ],
[ 'name' => 'Thorin Oakenshild', 'race' => 'Dwarf' ],
[ 'name' => 'Balin', 'race' => 'Dwarf'],
[ 'name' => 'Bifur', 'race' => 'Dwarf'],
// ...
];
$query->from($thorinsCompany);
$query->where('race', 'Dwarf')
$query->where('age', 50, '>');
$results = $query->findAll();
On a related note, Mongo PHP extension provides SQL to Mongo API translation, http://www.php.net/manual/en/mongo.sqltomongo.php. The basic logic could be used as a guiding example for someone who set their foot on developing SQL interface to PHP arrays.
I suppose the reason why there aren't any actively used SQL-like query languages for arrays in PHP is performance. Using something like ArrayQuery (which I am the author of) already results in a performance penalty. A query language would result in further decreased performance which makes most of the time no sense.
ArrayQuery was also inspired by Doctrines QueryBuilder, which is, a way to transform SQL into system based on objects that is more natural in a OOP language like PHP.
I'm working on a project and using Yii's Migration feature to keep the different production and test systems in sync. I must say i love this tool.
My question is there a way to create custom Abstract Data Types?
I know Yii's migration feature is made to allow table creation in multiple DBMS systems but my site is limited to MySQL so that should help things along.
What I would like to do is:
$this->createTable('test_table', array(
'key'=>'pk',
'active_YN'=>'yn',
));
instead of:
$this->createTable('test_table', array(
'key'=>'pk',
'active_YN'=>'TINYINT(1) NOT NULL DEFAULT \'1\'',
));
Im guessing that i would have to extend CDbMigration, possibly with a behavior?
Many Thanks.
If you still want to make the trick, here is what you can try.
MySQL uses the driver CMysqlSchema by default. You need to extend this class with your custom abstract column types.
class MyCustomMysqlSchema extends CMysqlSchema
{
/**
* #var array the abstract column types mapped to physical column types.
*/
public $columnTypes=array(
'pk' => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY',
'string' => 'varchar(255)',
'text' => 'text',
'integer' => 'int(11)',
'float' => 'float',
'decimal' => 'decimal',
'datetime' => 'datetime',
'timestamp' => 'timestamp',
'time' => 'time',
'date' => 'date',
'binary' => 'blob',
'boolean' => 'tinyint(1)',
'money' => 'decimal(19,4)',
// add your custom abstract column types here
'yn' => 'tinyint(1) UNSIGNED NOT NULL DEFAULT 1',
);
}
You need your connection to use this new driver. Modify your db configuration as follow.
'db'=>array(
// your initial db configuration here
'driverMap'=>array('mysql'=>'MyCustomMysqlSchema'),
),
I've been doing the SYMFONY jobeet tuto(day 10) and once in the FORMS section, I found that some times we use :
'category_id' => new sfWidgetFormDoctrineChoice(array('model' => $this->getRelatedModelName('JobeetCategory'), 'add_empty' => false)),
and sometimes we use simply
'jobeet_affiliates_list' => new sfWidgetFormDoctrineChoice(array('multiple' => true, 'model' => 'JobeetAffiliate')),
Can anyboody explain to me WHY? and HOW is it working ?
why ,sometimes,do we use getRelatedModelName?? and why somtimes 'model' => 'myModel'???
Pretty much same thing, but, you can only use getRelatedModelName when there's a relation between the current form's model and the model you need in your widget. For example, if there's a relation defined between Article and Category, you can use getRelatedModelName('Category') in ArticleForm (usually a relation is defined).
In both cases (when a relation exists/does not exist) you can just write the model as a string 'model' => 'JobeetAffiliate'. I usually do that.