How to make Psalm recognize variables from an included file - psalm-php

I have a config file that's included in a function, like this:
function getConnection() {
include 'config.php';
return new Connection($config['host']);
}
The issue is to make Psalm recognize the $config variable from the config file. Possible? Preferable using array shape notation.

Solved by adding a /** #var ... annotation above the include line:
function getConnection() {
/** #var array{host: string} $config */
include 'config.php';
return new Connection($config['host']);
}

Related

Symfony convert XLIFF files to another format (e.g. JSON or CSV)

I am using XLIFF files to handle Symfony 5.4 translations but my client would like to convert them to CSV or JSON.
Is it possible to convert my existing files to another format? I don't want to use the extract or update command because this would re-generate the translations. I would prefer to convert my existing ones in another format.
I also tried external tools such as xliff-to-json but they didn't work.
Since I couldn't find a suitable tool I created a console command which converts from XLIFF to CSV:
This is the link to the Gist, please feel free to suggest edits: https://gist.github.com/lukepass/6955d0cf25c44138df24f605e53a96cb
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Translation\Loader\XliffFileLoader;
#[AsCommand(
name: 'app:convert-translations',
description: 'Converts translation files from XLIFF to CSV.'
)]
class ConvertTranslationsCommand extends Command
{
private string $projectDir;
public function __construct(string $projectDir)
{
// best practices recommend to call the parent constructor first and
// then set your own properties. That wouldn't work in this case
// because configure() needs the properties set in this constructor
$this->projectDir = $projectDir;
parent::__construct();
}
protected function configure(): void
{
$this
->addArgument('locale', InputArgument::REQUIRED, 'Locale')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
/** #var string $locale */
$locale = $input->getArgument('locale');
$translationsDir = $this->projectDir.DIRECTORY_SEPARATOR.'translations';
// takes all the XLIFF files in the translations directory using the Finder component
$finder = new Finder();
$finder->files()->in($translationsDir)->name('*.'.$locale.'.xlf');
if (!$finder->hasResults()) {
$io->error('No XLIFF files found in the translations directory.');
return Command::FAILURE;
}
// iterates over all the XLIFF files found and converts them to CSV
foreach ($finder as $file) {
$xliffFileLoader = new XliffFileLoader();
$messageCatalogue = $xliffFileLoader->load($file->getRealPath(), $locale);
$translations = [];
foreach ($messageCatalogue->all('messages') as $id => $translation) {
$translations[$id] = $translation;
}
// replaces the XLIFF file extension with '.csv'
$csvFilePath = str_replace('.xlf', '.csv', $file->getRealPath());
// creates the CSV file and adds the BOM (Byte Order Mark)
// this is required to make LibreOffice being able to open it
$csvFile = fopen($csvFilePath, 'w');
// writes the actual CSV contents using ';' as the delimiter
foreach ($translations as $id => $translation) {
fputcsv($csvFile, [$id, $translation], ';');
}
fclose($csvFile);
$io->success(sprintf('XLIFF file "%s" converted to CSV.', $file->getFilename()));
}
return Command::SUCCESS;
}
}

How to override system.mail.yml in Drupal 8?

I have the below code in my file: core\modules\system\config\install\system.mail.yml
interface:
default: 'php_mail'
I want to change the code to:
interface:
default: 'SMTPMailSystem'
In order to get my SMTP module to work. On changing the code in the core file my module works. Since making direct changes in core file is not good I want to know how do we override such files. I am fairly new to Drupal 8 hence couldn't get through.
Drupal has an article on Configuration override system, which gives an overview and starter code to override configurations defined in *.yml . You can jump to "Providing overrides from modules" section right away for your case.
In short:
Create a module (config_example used as an example)
Create a config_example.services.yml, and put:
services:
config_example.overrider:
class: \Drupal\config_example\ConfigExampleOverrides
tags:
- {name: config.factory.override, priority: 5}
config.factory.override is the important thing here, others are up to you to change.
Define the class which implements ConfigFactoryOverrideInterface:
namespace Drupal\config_example;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
/**
* Example configuration override.
*/
class ConfigExampleOverrides implements ConfigFactoryOverrideInterface {
/**
* {#inheritdoc}
*/
public function loadOverrides($names) {
$overrides = array();
if (in_array('system.mail', $names)) { // (a)
$overrides['system.mail'] = [
'interface' => ['default' => 'SMTPMailSystem']
];
}
return $overrides;
}
/**
* {#inheritdoc}
*/
public function getCacheSuffix() {
return 'ConfigExampleOverrider'; // (c)
}
/**
* {#inheritdoc}
*/
public function getCacheableMetadata($name) {
return new CacheableMetadata();
}
/**
* {#inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
return NULL;
}
}
The following thing is changed to work for your case:
(a) The in_array needle is changed to system.mail which is the YML you wish to override. The value assigned to $overrides['system.mail'] is changed to what you wish to be placed.

Find all methods without documentation (preferably inside a directory) in PhpStorm

Is there a way to find all the methods without documentation in PhpStorm 10? What I can think of now is using regex, but I don't know how reliable it would be.
Code Inspect/PHPDoc section doesn't list any of them.
Ex:
Do not find this:
/**
* This method does something.
*
* #param int $name A parameter
*
* #returns array An array of something
*/
public function methodName($name)
{
return array();
}
Find This:
public function methodName($name)
{
return array();
}
Or maybe even this:
/**
* #param $name
* #returns
*/
public function methodName($name)
{
return array();
}
The name of the inspection you're looking for is "Missing PHPDoc comment", it's disabled by default. You can use Code | Run Inspection by Name or Code | Inspect Code to find all of the occurrences. Using these tools you can easily configure the desired scope.

Accessing config.yml variables in Bolt extensions

I've set up a clean extension and am trying to pull in info from the extension's config.yml file. Config.yml is placed inside the extension folder (at the same level as Extension.php).
At the moment I'm just testing to see if I can retrieve the config. Here's the whole Extension.php:
<?php
namespace Bolt\Extension\andyjessop\vimeo;
use Bolt\Events\CronEvent;
use Bolt\Events\CronEvents;
use Bolt\Application;
use Bolt\BaseExtension;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class Extension extends BaseExtension
{
public function initialize() {
$this->app->get('api/update_video_content', array($this, 'updateVideoContent'))
->bind('updateVideoContent');
}
public function getName()
{
return "vimeo";
}
public function updateVideoContent()
{
$config = $this->config['user_id'];
$response = $this->app->json($config);
return $response;
}
}
And in the config.yml:
access_token: xxxxxxxx
user_id: xxxx
api_base_url: https://api.vimeo.com/
But it returns an empty object. What am I doing wrong here?
Is your config called Config.yml.dist or config.yml.dist - note the capital C, it should be all lowercase? Other than that, after installation of your extension the config.yml.dist will be copied to app/config/extensions/{extensionname}.config.yml and the values in there will be used.

Using Fractal Transformer with ember-data

I am using PHP league's Fractal as the transformer for my API. However, I think I must be doing something wrong as the item transformer wraps everything in an array like it would a collection which is against the JSON API standard I believe.
So for a user with ID of one I get something like this:
{
"users":[
{
"id":1,
"firstName":"Jacob",
"surname":"Windsor",
}
]
}
When surely it should be this?
{
"users":
{
"id":1,
"firstName":"Jacob",
"surname":"Windsor",
}
}
I am using ember.js and this is causing problems with naming conventions.
I am using Laravel and in my userController I have something like this:
public function show($id)
{
$user = User::find($id);
return $this->respondItem($user);
}
Then in the apiController that everything extends from:
public function respond($response, $status = 200){
return Response::make($response, $status);
}
public function respondTransform($resource){
$fractal = new Fractal\Manager();
$fractal->setSerializer(new JsonApiSerializer());
return $this->respond($fractal->createData($resource)->toJson());
}
public function respondItem($data, $transformer = null, $namespace = null){
! isset($transformer) ? $transformer = $this->transformer : $transformer = $transformer;
! isset($namespace) ? $namespace = $this->namespace : $namespace = $namespace;
$resource = new Item($data, $transformer, $namespace);
return $this->respondTransform($resource);
}
I must be doing something wrong. The fractal docs have no examples specifically for items only collections so I am unsure what I have done.
So it seems that Fractal doesn't quite obey ember-data's conventions which is an annoying problem but very easily overcome using custom serialziers.
I have a psr-4 autoloaded file named CustomJsonSerializer which I have included in my ApiController class. If you follow the article on php league's site (posted above) its fairly easy to do. I have these two methods.
public function collection($resourceKey, array $data)
{
return array($resourceKey ?: 'data' => $data);
}
/**
* Serialize an item resource
*
* #param string $resourceKey
* #param array $data
*
* #return array
*/
public function item($resourceKey, array $data)
{
return [$resourceKey => $data];
}
You can see that the collection is responding as it normally would, i.e I haven't changed it. But the item method just responds without the extra array. Simple! You have to include all the other methods as well and I haven't got round to sorting out pagination but it should be fairly simple.
I hope this helps anyone wanting to use ember-data with Fractal. I highly recommend it, fractal has made my life so much easier. You could build transformers yourself but it makes it so much easier and more easily modified in the future.
Edit:
Please make sure you keep the $resourceKey in both the methods. You need to be using it and setting it when calling the transformer. |Ember-data requires a resource key.
Assuming your userController extends ApiController, you could simply do:
public function show($id)
{
$user = User::findOrFail($id);
return $this->setStatusCode(200)->withItem($user, new UserTransformer);
}
You do need to implement the UserTransformer class. If you need help with that, let me know in the comments.
I actually found that a much simpler adjustment of JsonApiSerializer did what I needed for Ember:
(I just took out the count($data) check)
<?php
namespace Acme\Serializer;
use RuntimeException;
use League\Fractal\Serializer\JsonApiSerializer;
class EmberSerializer extends JsonApiSerializer
{
/**
* Serialize the top level data.
*
* #param string $resourceKey
* #param array $data
*
* #return array
*/
public function serializeData($resourceKey, array $data)
{
if (! $resourceKey) {
throw new RuntimeException('The $resourceKey parameter must be provided when using '.__CLASS__);
}
return array($resourceKey => $data);
}
}