I'm creating a class that will have one public method, which returns a value indexed by a parameter. I have a single bundle at present. The directories inside the bundle I currently have are:
/Controller
/DataFixtures
/DependencyInjection
/Document
/Entity
/Resources
/Tests
What is the convention for placement of a class like this?
Symfony official website suggest src/AppBundle/Utils
Source : http://symfony.com/doc/current/best_practices/business-logic.html
Your question is a bit subjective, but according to what is outlined in Bundle Structure and Best Practices, a Bundle is just namespaced code. If the utility class is of first-grade, why don't you place it into the root-dir of the Bundle?
Namespace Bundle\HelloBundle;
Class Utility {
public static function returnIndexedValueByParameter($parameter) {
...
}
}
Filename:
Bundle/HelloBundle/Utility.php
Related
First off, the issue/question arose with Laravel 5.6 and PHP 7.4.10. I know that 'magic strings' for controller calls have been deprecated as of recent Laravel versions, but I'm interested in the underlying infrastructure in this question.
PREREQUISITES
Suppose I have two folders: project and core.
project contains a freshly installed laravel project;
core contains some package-like structure of namespaces and classes (not actually a package in the laravel sense), which is being autoloaded by the project.
For a minimal working example, you can see the last commit of the repository I published about this. I'm going to post the main files' contents here as well, but you can actually reproduce and play around with the sample projects yourself.
Basically the concept is pretty standard, much like a package: I want to have some reusable logic inside of my core with some default implementations. In the project I may or may not overwrite some of the functions, but for the sake of the question's simplicity, my core only defines a Fruit module (just a folder with a namespace) containig three files:
fruit_routes.php
<?php
Route::get('/pear', 'FruitController#pear');
Route::get('/apple', 'FruitController#apple');
FruitController (This is where the question is focused)
<?php
namespace CORE\Fruit;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class FruitController extends Controller
{
public function pear()
{
//if we call the action '\CORE\Fruit\FruitController#apple' instead, it works :)
return redirect()->action('FruitController#apple', ['message' => "No pears available! :( "]);
}
public function apple(Request $request)
{
return $request->message . "There you have an apple!";
}
}
FruitServiceProvider
<?php
namespace CORE\Fruit;
use Illuminate\Support\ServiceProvider;
class FruitServiceProvider extends ServiceProvider
{
public function register()
{
/*
* Map routes from our Fruit module to the controller's namespace
*/
$this->app->router->namespace('CORE\Fruit')->group(__DIR__."/fruit_routes.php");
}
}
And then in the project I simply include the following two lines:
composer.json
{
//..
"autoload":
//...
"psr-4": {
"App\\": "app/",
"CORE\\" : "../core"
}
},
}
config/app.php in $providers array
/*
* Package Service Providers...
*/
CORE\Fruit\FruitServiceProvider::class,
Again, for the full sample structure and contents, refer to the MWE repo.
THE QUESTION
Although the namespace for the controller classes has been explicitly declared from the FruitServiceProvider's register() method for the fruit_routes.php file, Laravel still fails to recognize that same namespace when using rediret()->action() from within those same controllers.
In the given FruitController you can see:
return redirect()->action('FruitController#apple');
and this will fail with Action App\Http\Controllers\FruitController#apple not defined.
As you can see, Laravel still searches for the controller inside the current project's default controller namespace (in this case: App\Http\Controllers)
Fortunately, passing the fully qualified name of the controller solves the issue:
return redirect()->action([self::class, 'apple']);
//Or alternativelly, but really clumsy:
//return redirect()->action('\Core\Fruit\FruitController#apple'); //notice the slash at the beginning
I have the suspicion that I must somehow also inform the redirect() helper (and actually, the underlying Redirect facade) that I want it to reference the controllers inside of my CORE\Fruit namespace whenever called from within that namespace... Or what would be the right approach? Is that desired behavior and am I missing something? And again, as mentioned in the beginning, I know that 'magic strings' are not the best practice and the most beloved feature of Laravel, but I really want to understand how things work in this scenario.
I also found this issue on github with a realted topic, but no answer there as well. As for people who are about to comment 'just avoid using magic strings', I'm stuck with a project I inherited and rewriting all of the core's magic string controller references is really not an option I would love to embrace.
Any insight is appreaciated! Many many thanks in advance!
I got my some custom classes in my vendor folder which I use in my Symfony project. Now I need to access some parameters from my parameters.yml which is located in
C:\xampp\htdocs\myproject\app\config\parameters.yml
In my regular Symfony code I just do
$this->getParameter('myparameter');
and all set, but not in vendor folder. I guess I need to import some namespaces, but could not find which?
Any help would be appreciated. Thank you.
UPD1 The issue was solved by adding the following code to AppBundle.php
class AppBundle extends Bundle
{
private static $containerInstance = null;
public function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container = null)
{
parent::setContainer($container);
self::$containerInstance = $container;
}
public static function getContainer()
{
return self::$containerInstance;
}
}
and then calling the container from my vendor code with the following:
use AppBundle\AppBundle;
AppBundle::getContainer()->getParameter('myparameter');
Thanks everyone for help.
DependencyInjection/YourBundleExtension.php
$container->setParameter('myparameter', $config);
https://symfony.com/doc/current/create_framework/dependency_injection.html#main
After that, add this config to your service (for example)
Resources/config/services.yml
services:
your.service:
class: App\YourBundle\Service\YourService
arguments:
- %myparameter%
You can either define your services in
C:\xampp\htdocs\myproject\app\config\services.yml
The Resources\config directory of a bundle. See How to Load Service Configuration inside a Bundle.
You simply define your services by giving them a name, specifying the class and then listing any arguments the class has to be injected into it when it is instantiated. To reference a parameter from parameters.yml you wrap the name of the parameter in %% as I did below.
services:
my_project.some_service:
class: Vendor\Class\Name\Here
arguments: ['%some.parameter.name%']
In 99% of scenarios you definitely should not inject the dependency injection container into your own classes. Having your classes depend on the container is not good - have them depend on other services from your project or simple values such as strings, integers etc.
I just want to know what is the best practise/way of doing this. I'm going to explain with an example to make it easier to understand.
Note: So far example below work fine if I have only one bundle in my application. Question is at the bottom of the post.
Thanks in advance
SingleBundle/Resources/config/services.yml
services:
form_errors:
class: Hello\SingleBundle\Services\FormErrors
FormErrors.php
namespace Hello\SingleBundle\Services;
use Symfony\Component\Form\FormInterface;
class FormErrors
{
public function getErrors(FormInterface $form)
{
.......
.......
return $errors;
}
}
QUESTION:
How do I avoid duplicating these two files if I have more than one bundles in my application? Where do I define service and the service class whcih will be accesible from all the bundles?
You have to define your service in the bundle which implements logic of this service. If you have bundle SingleBundle the best way to call the service is to use special prefix (single_bundle.form_errors in your case).
If you have two or more bundles it's not necessary to duplicate service definition and service class declaration because all services defined in the namespace of one bundle (which is properly loaded to the project) are accessible in the namespace of the other bundle (which is properly loaded to the project as well).
So, I think before create a service you just need to think where it should be defined. And take care about service name if you have any doubts about possible duplicates.
I have ZF2 module, and simultaneously I use Propel genereted models hosted in root-directory/generated-classes. Can I make them share selfsame namespace - like Bookstore or so?
From Zend\Loader\StandardAutoloader I see:
public function registerNamespace($namespace, $directory)
{
$namespace = rtrim($namespace, self::NS_SEPARATOR) . self::NS_SEPARATOR;
$this->namespaces[$namespace] = $this->normalizeDirectory($directory);
return $this;
}
so if I provide two directories in Module.php, the last will prevail.
There is also:
public function setFallbackAutoloader($flag)
{
$this->fallbackAutoloaderFlag = (bool) $flag;
return $this;
}
Can I resort to it and how do I leverage this option? Any other (better) options?
I wouldn't put my models directly in /your-application/root. This will be against ZF2's recommended directory scaffolding. Instead of that, I'd create a /FooModule/src/FooModule/Model directory and put all of my models inside this folder using namespace FooModule\Model namespace definition in model class.
Another detail is; trying to pointing two different directories for same namespace is absolutely bad idea. This will be against PSR-4 Autoloading Standard and lot of open source libraries & frameworks including Zend Framework 2 which heavily depends on this standard.
I would look at the problem from a different angle. Just ask: Why I need to point one of my namespaces to the two different directories?
I think actually you mean Domain Entities by "Propel generated models". If this is correct (i mean Bookstore) is an Entity rather than a Model. You may also want to read this great answer.
So, you can try to create an Entity namespace in your Application (or whatever) ZF2 module and write your Entity classes under a sub-namespace inside that. This is perfectly valid. For example:
Application\src\Entity\Bookstore.php - namespace is Application\Entity
Application\src\Entity\Book.php - namespace is Application\Entity
Application\src\Entity\Author.php - namespace is Application\Entity
Or this is also valid scenario too (Bookstore is a module):
Bookstore\src\Entity\Book.php - namespace is Bookstore\Entity
Bookstore\src\Entity\Author.php - namespace is Bookstore\Entity
In both example scenarios, Book.php and Author.php are your auto-generated domain entities and they shares same namespace while not conflicting ZF2 or PSR-4 autoloading mechanisms.
I'm creating a control panel application that has a base bundle with some basic functionality and specific bundles for advanced and specific functionality.
for example the base bundle handles user authentication and holds all the template assets and other bundles add functionalities to config different parts of the operating system.
I need to be able to add menu links in the layout of the base bundle to each of the other bundles. and I prefer to do it in each bundles configuration so I can mix and match features for different clients.
I read all about Compiler Passes, Extensions and dependency injection with no luck. is there correct of doing it ?
If you are using Twig this should do the trick...
{% render "DifferentBundle:ControllerName:functionalityName" with {'argument_name': 3} %}
You should have a functionalityNameAction method in your DifferentBundle controller for this to work.
Take a look at the Creating and using Templates - Embedding Controllers section in the doc.
Hope it helps.
Just in case anyone has a similar problem, here is how I achieved this:
I created a service in my BaseBundle that implements the __get , __set, __isset and __unset magic methods and has an extra append method. it stores variables in a static variable inside the class.
I then added Listeners to all my bundles :
namespace Mbs\OtherBundle\Listener;
use Mbs\BaseBundle\Services\GlobalVars;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class ControllerListener
{
protected $_global_vars;
public function __construct(GlobalVars $global_vars)
{
$this->_global_vars = $global_vars;
}
public function onKernelController(FilterControllerEvent $event)
{
$this->_global_vars->append('bundles', 'mbs.other');
}
}
This is my services.yml for one of the bundles. GlobalVars is the class I mentioned earlier.
services:
mbs.base_controller_listener:
class: Mbs\OtherBundle\Listener\ControllerListener
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
arguments: [ #mbs.global_vars ]