Zend framework 3 routers in separate file (not in config array tree) - zend-framework3

I am using this tutorial https://docs.zendframework.com/tutorials/getting-started/overview/ for creating album module. It works for me.
Inside project there is /module/Album/config/module.config.php file which contains routes. Routers are located inside an array tree. As my previous experience shows I can have in the future dozens of routes per a project (even per a module).
On this documentation page https://docs.zendframework.com/zend-router/routing/ I found another way to add routers to the module.
// One at a time:
$route = Literal::factory([
'route' => '/foo',
'defaults' => [
'controller' => 'foo-index',
'action' => 'index',
],
]);
$router->addRoute('foo', $route);
Such a way is preferred for me than storing routes in a very deep config array tree.
So, my question is: where I can put php routers code outside a config tree as I have mentioned earlier? Where in the module should be such a routers-file located at?

Next to module.config.php in the modules config/ folder it's common to create a routes.config.php.
I split it further by doing something like user.routes.config.php with roles.routes.config.php. Possibly you'd like front.routes.config.php with admin.routes.config.php.
In the end, it's up to you. For colleagues and future sanity, make sure you do it consistently though.
As an example, the config in a project of mine for the User module:
It's a module that handles anything directly User related, so it's all in there. Should probably split it up more, but for now, that would be unnecessary.
You'd then load all of this config like so in your Module.php:
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
class Module implements ConfigProviderInterface, AutoloaderProviderInterface
{
/**
* #return array
*/
public function getConfig()
{
$config = [];
$path = __DIR__
. DIRECTORY_SEPARATOR . '..'
. DIRECTORY_SEPARATOR . 'config'
. DIRECTORY_SEPARATOR . '*.php';
foreach (glob($path) as $filename) {
$config = array_merge_recursive($config, include $filename);
}
return $config;
}
/**
* #return array
*/
public function getAutoloaderConfig()
{
return [
'Zend\Loader\StandardAutoloader' => [
'namespaces' => [
__NAMESPACE__ => __DIR__ . DIRECTORY_SEPARATOR . 'src',
],
],
];
}
}
Remember, eventual implementation in your project(s) is up to you. However, work out a standard and stick to it. You'll go insane if you have different standards everywhere you go.

Related

How to configure a Translatable Entity in EasyAdmin?

I'm using Translatable and EasyAdmin in a Symfony 5 project and I have configured 2 languages.
The issue is I need to be able to edit the different languages of a record in EasyAdmin, I have checked the docs of Translatable, EasyAdmin and Symfony, There is very little information about how to integrate database translations into EasyAdmin.
Therefore, I'm a bit stuck in terms of code, I have tried configuring setTranslationParameters() inside the entity CRUD controller and changing some configuration in the DashboardController however, I don't think this is the right approach.
Any suggestions of how to solve this issue?
Thank you for your effort and time.
as of writing, this feature doesn't exist in EasyAdmin, please see the link to the answer to the issue on Github.
https://github.com/EasyCorp/EasyAdminBundle/issues/4982
However, a work around is possible with a different package:
remove doctrine-extensions/DoctrineExtensions and then install KnpLabs/DoctrineBehaviors
install a2lix/translation-form-bundle
Create a translation field:
<?php
declare(strict_types=1);
namespace App\Controller\Admin\Field;
use A2lix\TranslationFormBundle\Form\Type\TranslationsType;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
final class TranslationField implements FieldInterface
{
use FieldTrait;
public static function new(string $propertyName, ?string $label = null, array $fieldsConfig = []): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setFormType(TranslationsType::class)
->setFormTypeOptions([
'default_locale' => 'cz',
'fields' => $fieldsConfig,
]);
}
}
Use the TranslationField inside your admin CRUD controller:
public function configureFields(string $pageName): iterable
{
return [
TextField::new('title', 'title')->hideOnForm(),
TranslationField::new('translations', 'translations', [
'title' => [
'field_type' => TextType::class,
'required' => true,
]
// add more translatable properties into the array
])->setRequired(true)
->hideOnIndex()
];
}

Symfony validator translations

I just started using symfony validator and i really like it except translation part, currently it uses my own translator lib, but i found validator.LOCALE.xlf files where are translations for almost all languages stored, and i can't figure out how to use them.
My current validator registering code is
$container->register('validator', \Symfony\Component\Validator\Validator\ValidatorInterface::class)
->setFactory(
[
new Reference('validator.builder'),
'getValidator'
]
);
$container->register('validator.builder', \Symfony\Component\Validator\ValidatorBuilderInterface::class)
->setFactory(
[
\Symfony\Component\Validator\Validation::class,
'createValidatorBuilder'
]
)
->addMethodCall(
'setTranslator',
[
new Reference('translator') // Symfony translatorInterface
]
)
->addMethodCall(
'setTranslationDomain',
[
'messages'
]
);
It looks like i checked already whole validator structure, like RecursiveValidator, ContextualValidator, Contexts and etc, but just somewhere missing one single param, on another hand ConstraintViolationBuilder just simply takes passed translator and trying to translate constraint message through it, no attempts to use any xlf files.
Just force search through all validator library files gave no result too.
Symfony guilde didn't helped too, because it offers to use default error sentences as a translation key, and use this "keys" in your own translations files, but why copy already translated sentences to your own file, and also create a mess with keys pattern (for example i use snake case) when there is already structured files exists (i am talking about .xlf)?
Solution is to add xlf file loader to your translator, and pass it .xlf translations as a resource.
Something like that
$container->register('translator.xlf_file_loader', \Symfony\Component\Translation\Loader\XliffFileLoader::class);
$container->register('translator.php_file_loader', \Symfony\Component\Translation\Loader\PhpFileLoader::class);
$container->register('translator', \Project\Framework\Translation\Translator::class)
->addArgument(
new Reference('service_container')
)
->addMethodCall(
'addLoader',
[
'php',
new Reference('translator.php_file_loader')
]
)
->addMethodCall(
'addLoader',
[
'xlf',
new Reference('translator.xlf_file_loader')
]
)
->addMethodCall('addResource', ['php', __DIR__ . '/../translation/lt.php', 'lt'])
->addMethodCall('addResource', ['php', __DIR__ . '/../translation/en.php', 'en'])
->addMethodCall('addResource', ['php', __DIR__ . '/../translation/ru.php', 'ru'])
->addMethodCall('addResource', ['xlf', __DIR__ . '/../../vendor/symfony/validator/Resources/translations/validators.lt.xlf', 'lt'])
->addMethodCall(
'setFallbackLocales',
[
['lt']
]
);
Have you simply tried to either
change the locale in the app configuration:
# config/packages/translation.yaml
framework:
default_locale: 'en'
translator:
fallbacks: ['en']
Documentation
change the locale based on (user) input:
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
// some logic to determine the $locale
$request->setLocale($locale);
}
Documentation

Virtual Filesystem for PHPUnit tests in Laravel 5.4

i'm having a bit of a problem with my PHPUnit integration tests, i have a method which handles a form upload for a video file as well as a preview image for that video.
public function store($request)
{
/** #var Video $resource */
$resource = new $this->model;
// Create a new Content before creating the related Photo
$contentRepo = new ContentRepository();
$content = $contentRepo->store($request);
if($content->isValid()) {
$resource->content_id = $content->id;
$directory = 'frontend/videos/assets/'.date("Y").'/'.date('m').'/'.time();
\File::makeDirectory($directory, 0755, true);
$request->video->move($directory.'/', $request->video->getClientOriginalName());
$resource->video = '/'.$directory.'/'.$request->video->getClientOriginalName();
$request->preview_image->move($directory.'/', $request->preview_image->getClientOriginalName());
$resource->preview_image = '/'.$directory.'/'.$request->preview_image->getClientOriginalName();
$resource->highlighted = intval($request->input('highlighted') == 'on');
$resource->save();
return $resource;
}
else {
return $content;
}
}
The important part to keep is the $request->video->move() call which i probably need to replace in order to use Virtual Filesystem.
and then the test
public function testVideoUpload(){
File::put(__DIR__.'/frontend/videos/assets/image.mp4', 'test');
$file = new UploadedFile(__DIR__.'/frontend/videos/assets/image.mp4', 'foofile.mp4', 'video/mp4', 100023, null, $test=true);
File::put(__DIR__.'/frontend/images/assets/image.jpg', 'test');
$preview = new UploadedFile(__DIR__.'/frontend/images/assets/image.jpg', 'foofile.jpg', 'image/jpeg', 100023, null, $test=true);
$this->post('/admin/videos', [
'title' => 'My Video #12',
'description' => 'This is a description',
'actors' => [$this->actor->id, $this->actor2->id],
'scenes' => [$this->scene->id, $this->scene2->id],
'payment_methods' => [$this->paymentMethod->id],
'video' => $file,
'preview_image' => $preview
])->seeInDatabase('contents', [
'title' => 'My Video #12',
'description' => 'This is a description'
]);
}
As you can see, i need to create a dummy file in some local directory and then use that in the HTTP request to the form's endpoint, then after that, that file would be moved and i need to delete the created folder and the new moved file... it's an authentic mess.
As such i want to use Virtual Filesystem instead, but i have no idea how to set it up in this particular case, i've already downloaded a package and set it up, but the questions are, first, which package have you used/recommend and how would you tweak the class and the test to support the Virtual Filesystem? Would i need to switch over to using the Storage facade instead of the $request->video->move() call? If so how would that be done exactly?
Thank you in advance for your help
I couldn't figure out the VFS system, however i do have somewhat of an alternative that's still kinda messy but gets the job done.
Basically i set up two methods on my PHPUnit base class to setup and teardown the temp folders i need on any test that requires them, because i'm using Database Transactions the files get deleted on every test run and i need to create new dummy files every time i run the test.
So i have two methods setupTempDirectories and teardownTempDirectories which i will call at the beginning and at the end of each test that requires those temporary directories.
I put my temp files in the Storage directory because sometimes i run my tests individually through PHPStorm and the __DIR__ command gets messed up and points to different directories when i do that, i also tried __FILE__ with the same result, so i just resorted to using Laravel's storage_path instead and that works fine.
Then that leaves the problem of my concrete class which tries to move files around and create directories in the public folder for them... so in order to fix that i changed the code to use the Storage facade, then i Mock the Storage facade in my tests
So in my concrete class
$directory = 'frontend/videos/assets/'.date("Y").'/'.date('m').'/'.time();
Storage::makeDirectory($directory, 0755, true);
Storage::move($request->video, $directory . '/' . $request->video->getClientOriginalName());
$resource->video = '/'.$directory.'/'.$request->video->getClientOriginalName();
Storage::move($request->preview_image, $directory . '/' . $request->preview_image->getClientOriginalName());
$resource->preview_image = '/'.$directory.'/'.$request->preview_image->getClientOriginalName();
And then in my test i mock both the makeDirectory and the move methods like such
// Override the Storage facade with a Mock version so that we don't actually try to move files around...
Storage::shouldReceive('makeDirectory')->once()->andReturn(true);
Storage::shouldReceive('move')->twice()->andReturn(true);
That makes my tests work and does not actually leave files behind after it's done...i hope someone has a better solution but for the time being this is what i came up with.
I was actually trying to use VFS but it never worked out... i keep getting errors that the original file in the storage directory is not found even though it's right there...
I'm not even sure the Storage facade was using VFS in the background to begin with even though it should...

Symfony 3.0 load config from php file

I try to set up a symfony project with the microcontroller trait. But instead of use a config.yml I want to use a config.php file.
return [
'framework' => [
'secret' => 'secret_'
]
];
What is the best practice to achieve this?
when using microkernel trait, you can use the configureContainer method in your front controller (app.php) to load configuration directly from an array, like this:
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
{
// PHP equivalent of config.yml
$c->loadFromExtension('framework', array(
'secret' => 'S0ME_SECRET'
));
}
docs here
You should use the container to set the parameters like
$container->setParameter('framework.secret', 'secret_');
as explained in the Symfony Docs

Drupal node_type_get_type() and hook_uninstall()

I have a module job_post which installs job_post content type.
In this module I have hook_unistall() which calls for node_type_delete() function which removes my content type.
After unistalling process I have errors from Drupal's core module comment which fires from node_type_delete() after module_invoke_all('node_type_delete', $info).
Error is the following and repeats 8 times (because of the loop in comment_node_type_delete()):
Notice: Trying to get property of non-object in comment_node_type_delete()
(line 343 of ....\comment.module).
I have this error because $info variable in node_type_delete() function is false.
My question is, why when my module is installed and when I'm printing var_dump(node_type_get_type('job_post')) on any page, I have an object, but when I'm trying to print the same code in my unistall function I get false and this error?
job_post.install
/**
* Implements hook_install().
*/
function job_post_install() {
node_types_rebuild();
$types = node_type_get_types();
node_add_body_field($types['job_post']);
$body_instance = field_info_instance('node', 'body', 'job_post');
$body_instance['type'] = 'text_summary_or_trimmed';
field_update_instance($body_instance);
}
/**
* Implements hook_uninstall().
*/
function job_post_uninstall() {
$instances = field_info_instances('node', 'job_post');
foreach ($instances as $instance_name => $instance) {
field_delete_instance($instance);
}
// Force rebuild of the node type cache
// as Clive suggested didn't help
// _node_types_build(TRUE);
node_type_delete('job_post');
field_purge_batch(1000);
}
job_post.module
/**
* Implements hook_node_info() to provide our job_post type.
*/
function job_post_node_info() {
return array(
'job_post' => array(
'name' => t('Job Post'),
'base' => 'job_post',
'description' => t('Use this content type to post a job.'),
'has_title' => TRUE,
'title_label' => t('Job Title'),
'help' => t('Enter the job title and job description')
)
);
}
/**
* Implement hook_form() with the standard default form.
*/
function job_post_form($node, $form_state) {
return node_content_form($node, $form_state);
}
Note: This module example was taken from Pro Drupal 7 Development book (page 141) with minor changes and it was given errors even with original.
Hi by referring to documentation an core modules of Drupal 7. There is a usage problem.
hook_node_info defines a content type automatically. The content types which are created in this way, uninstalled-disable automatically.
Core blog module defines hook_node_info but does not operate any node_type_delete on hook_uninstall
http://api.drupal.org/api/drupal/modules%21blog%21blog.module/function/blog_node_info/7
When you call node_type_delete('job_post'); on hook_uninstall, node type info has already gone. Because of that comments module raises error.
Normally you should only remove any data related to your content type. And let the rest to be done by core.
Additionally if you really want create/delete your content type, you may not use hook_node_info. You can manually create/delete content type on install/uninstall hook.
Sample uninstall here:
http://public-action.org/content/drupal-7-field-api-drupal-7-adding-custom-content-type-custom-fields-field-api
It seems like the node type cache hasn't been built fully for some reason, try forcing the rebuild before you call node_type_delete() and all node types should be made available:
// Force rebuild of the node type cache
_node_types_build(TRUE);
// Delete our content type
node_type_delete('job_post');

Resources