I'm working on small PHP framework (for learning). It has a file with LoginController class containing a method with Route attribute (code below). Is there any way to get the name of the method in the Route attribute class using Reflection?
Class with method using attribute:
class LoginController {
#[Route('GET', '/login')]
public function index() {
// some code
}
}
"Route" attribute class:
use Attribute;
use ReflectionMethod;
#[Attribute]
class Route {
public function __construct($method, $routeUri) {
// Can I get the method name ("index") from attribute instead of writing it?
// And the class name?
$reflection = new ReflectionMethod(\App\Controllers\LoginController::class, 'index');
$closure = $reflection->getClosure();
// Register a route...
Router::add($method, $routeUri, $closure);
}
}
Reflection is an option, but please be aware that you will be looping over all the attributes of all the methods in a class (at least until a matching one is found). Of course, if all routes need to be registered, this isn't that bad.
$classRef = new ReflectionClass(LoginController::class);
foreach ($classRef->getMethods() as $method) {
$methodRef = new ReflectionMethod($method->class, $method->name);
foreach ($methodRef->getAttributes() as $attribute) {
if (
$attribute->getName() === 'Route'
&& $attribute->getArguments() === [$method, $routeUri]
) {
// you can register your route here
}
}
}
As far as classes go, the easiest way to go is just make an array with all your controller class names. There are packages out there that can detect all classes in a given namespace, which could be used for autodetecting your controllers.
Related
Okay there are questions about the same topic before but they don't help to fully understand this topic
SO SuggestionFirst
SO Suggestion Second
All the code is just to illustrate the situation, So this is the structure
A helper function which does something
namespace App\Helpers;
class Pets{
public function limit($string,$limit,$start = 0){
return substr($string,$start,$limit);
}
}
Now in order to use this helper, since it's a class so i need to create an object like this
CODE SAMPLE FIRST
namespace App\Objects;
use App\Helpers\Pets;
class User{
public function getShortUserName(){
$name = auth()->user()->first_name.' '.auth()->user()->last_name;
$pet = new Pets;
return $pet->limit($name,10);
}
}
But somewhere I got to know that if you add Facades before your namespace, you can call the function statically even if they are non static function like this
CODE SAMPLE SECOND
namespace App\Objects;
use Facades\App\Helpers\Pets;
class User{
public function getShortUserName(){
$name = auth()->user()->first_name.' '.auth()->user()->last_name;
return Pets::limit($name,10);
}
}
Now what I want to know is I have 2 sample codes with namespace as follows
use App\Helpers\Pets;
use Facades\App\Helpers\Pets;
By adding the Facades I can call the function statically but how, that's not a valida namespace in my app
What laravel doing behind the scene, I am so confused
Thank you for your time ;)
What you are describing is Laravels Real-Time Facades.
You can find documentation of the functionality here:
https://laravel.com/docs/6.x/facades#real-time-facades
I will not enter too much in details but this is a simple explanation of what's behind the scenes when you use facades in laravel.
Let's suppose you define a custom class with some public methods:
namespace Test;
class Foo
{
public function test()
{
return 'test';
}
}
Then you have to define a facade for this class:
namespace Test1;
class BarFacade
{
// In laravel this is called in the Facade abstract class but it is actually implemented
// by all the facades you add across the application
public static function getFacadeAccessor()
{
// In laravel you can also return a string which means that the object
// will be retrieved from the container.
return new \Test\Foo();
}
// In laravel this method is defined in the Facade abstract class
public static function __callStatic($method, $args)
{
$object = self::getFacadeAccessor();
return call_user_func_array([$object, $method], $args);
}
}
Then, you have to define the alias in the $aliases array of the config.app file. These aliases are parsed by laravel and registered using the php built-in function class_alias (see Illuminate/Foundation/AliasLoader.php)
class_alias('Test\Foo', 'BarFacade', true);
// You can also create an alias for the facade itself
class_alias('Test1\BarFacade', 'FooBar', true);
Then you can simply call the facades:\
var_dump(BarFacade::test());
var_dump(\Test1\BarFacade::test());
var_dump(\FooBar::test());
The results would obviously be:
string(4) "test"
string(4) "test"
string(4) "test"
What is the best practice for distributing controllers code with Laravel?
Example:
TaskController consumes particulary request and access specific model methods with task list as a result.
OrganisationTaskTreeController consumes different request accessing same methods on Task Entity but also gets OrganisationTree resource with method shared with OrganisationController.
Code:
class TaskController extends BaseController {
public function getTask(Request $request)
{
$_match = [];
if ($request->has('types'))
{
$_match['type'] = ['$in' => $request->get('types')];
}
if ( ! $request->has('group'))
{
throw new InvalidParameter("Undefined group parameter");
}
....
}
class OrganisationTaskTreeController extends BaseController {
public function getOrganizationTree(Request $request)
{
$_match = [];
$_tree = [];
if ($request->has('types'))
{
$_match['type'] = ['$in' => $request->get('types')];
}
if ( ! $request->has('group'))
{
throw new InvalidParameter("Undefined group parameter");
}
if ($request->has('unti'))
{
$_tree['unit'] = $request->get('unit');
}
....
}
}
How not to duplicate this code?
I think Controllers should not be extended by design, because of using methods from many controllers. It will be an overkill.
I'm thinking about:
Traits
HMVC
Controller as Service
or will it be good to make a Respository which consumes $request?
Perhaps you could refactor the code you want to reuse to a method on a base class that the two classes can extend, eg class TaskController extends BaseController, BaseClass. Then you could use the method on every class that extends the BaseClass.
How about a custom request object that you can typehint into whichever method that needs it for that specific validation? https://www.laravel.com/docs/5.2/validation#form-request-validation
I am using Laravel 5.1 and would like to access an array on the Model from the Trait when the Model before the model uses the appends array.
I would like to add certain items to the appends array if it exists from my trait. I don't want to edit the model in order to achieve this. Are traits actually usable in this scenario or should I use inheritance?
array_push($this->appends, 'saucedByCurrentUser');
Here is how my current setup works.
Trait
<?php namespace App;
trait AwesomeSauceTrait {
/**
* Collection of the sauce on this record
*/
public function awesomeSauced()
{
return $this->morphMany('App\AwesomeSauce', 'sauceable')->latest();
}
public function getSaucedByCurrentUserAttribute()
{
if(\Auth::guest()){
return false;
}
$i = $this->awesomeSauced()->whereUserId(\Auth::user()->id)->count();
if ($i > 0){
return true;
}
return false;
}
}
Model
<?php namespace App;
use App\AwesomeSauceTrait;
use Illuminate\Database\Eloquent\Model;
class FairlyBlandModel extends Model {
use AwesomeSauceTrait;
protected $appends = array('age','saucedByCurrentUser');
}
What I would like to do is something to achieve the same effect as extending a class. I have a few similar traits, so using inheritance gets somewhat ugly.
trait AwesomeSauceTrait {
function __construct() {
parent::__construct();
array_push($this->appends, 'saucedByCurrentUser');
}
}
I have seen some workarounds for this, but none of them seem better/cleaner than just adding the item to the array manually. Any ideas are appreciated.
Update
I discovered this way of accomplishing what I need for one trait, but it only works for one trait and I don't see an advantage of using this over inheritance.
trait
protected $awesomeSauceAppends = ['sauced_by_current_user'];
protected function getArrayableAppends()
{
array_merge($this->appends, $this->awesomeSauceAppends);
parent::getArrayableAppends();
}
How I am currently handling my Model, for what it is worth.
model
public function __construct()
{
array_merge($this->appends, $this->awesomeSauceAppends);
}
Traits are sometimes described as "compiler-assisted copy-and-paste"; the result of using a Trait can always be written out as a valid class in its own right. There is therefore no notion of parent in a Trait, because once the Trait has been applied, its methods are indistinguishable from those defined in the class itself, or imported from other Traits at the same time.
Similarly, as the PHP docs say:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved.
As such, they are not very suitable for situations where you want to mix in multiple variants of the same piece of behaviour, because there is no way for base functionality and mixed in functionality to talk to each other in a generic way.
In my understanding the problem you're actually trying to solve is this:
add custom Accessors and Mutators to an Eloquent model class
add additional items to the protected $appends array matching these methods
One approach would be to continue to use Traits, and use Reflection to dynamically discover which methods have been added. However, beware that Reflection has a reputation for being rather slow.
To do this, we first implement a constructor with a loop which we can hook into just by naming a method in a particular way. This can be placed into a Trait of its own (alternatively, you could sub-class the Eloquent Model class with your own enhanced version):
trait AppendingGlue {
public function __construct() {
// parent refers not to the class being mixed into, but its parent
parent::__construct();
// Find and execute all methods beginning 'extraConstruct'
$mirror = new ReflectionClass($this);
foreach ( $mirror->getMethods() as $method ) {
if ( strpos($method->getName(), 'extraConstruct') === 0 ) {
$method->invoke($this);
}
}
}
}
Then any number of Traits implementing differently named extraConstruct methods:
trait AwesomeSauce {
public function extraConstructAwesomeSauce() {
$this->appends[] = 'awesome_sauce';
}
public function doAwesomeSauceStuff() {
}
}
trait ChocolateSprinkles {
public function extraConstructChocolateSprinkles() {
$this->appends[] = 'chocolate_sprinkles';
}
public function doChocolateSprinklesStuff() {
}
}
Finally, we mix in all the traits into a plain model, and check the result:
class BaseModel {
protected $appends = array('base');
public function __construct() {
echo "Base constructor run OK.\n";
}
public function getAppends() {
return $this->appends;
}
}
class DecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
}
$dm = new DecoratedModel;
print_r($dm->getAppends());
We can set the initial content of $appends inside the decorated model itself, and it will replace the BaseModel definition, but not interrupt the other Traits:
class ReDecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
protected $appends = ['switched_base'];
}
However, if you over-ride the constructor at the same time as mixing in the AppendingGlue, you do need to do a bit of extra work, as discussed in this previous answer. It's similar to calling parent::__construct in an inheritance situation, but you have to alias the trait's constructor in order to access it:
class ReConstructedModel extends BaseModel {
use AppendingGlue { __construct as private appendingGlueConstructor; }
use AwesomeSauce, ChocolateSprinkles;
public function __construct() {
// Call the mixed-in constructor explicitly, like you would the parent
// Note that it will call the real parent as well, as though it was a grand-parent
$this->appendingGlueConstructor();
echo "New constructor executed!\n";
}
}
This can be avoided by inheriting from a class which either exists instead of the AppendingGlue trait, or already uses it:
class GluedModel extends BaseModel {
use AppendingGlue;
}
class ReConstructedGluedModel extends GluedModel {
use AwesomeSauce, ChocolateSprinkles;
public function __construct() {
// Standard call to the parent constructor
parent::__construct();
echo "New constructor executed!\n";
}
}
Here's a live demo of all of that put together.
I thought I'd add an update for 2019 since this was one of the first discussions that popped up when trying to do a similar thing. I'm using Laravel 5.7 and nowadays Laravel will do the reflection that IMSoP mentioned.
After the trait has been booted, Laravel will then call initializeTraitName() on the constructed object (where TraitName is the full name of the trait).
To add extra items to $appends from a trait, you could simply do this...
trait AwesomeSauceTrait {
public function initializeAwesomeSauceTrait()
{
$this->appends[] = 'sauced_by_current_user';
}
public function getSaucedByCurrentUserAttribute()
{
return 'whatever';
}
}
KISS:
I don't see any reason why you should use trait when your are simply appending attributes.
I would only recommend using trait without a constructor like you were doing, only if you model is getting pretty bulky and you wish to slim down things.
Please also note this not the correct way of appending attribute
protected $appends = array('age','saucedByCurrentUser');
You could do this:
protected $appends = array('age','sauced_by_current_user');
Appends attribute names should the snake_case of its method Name
Edited:
The idea behind appends is to dynamically add fields that doesn't exist in your database table to your model so after you can do like:
$model = FairlyBlandModel ::find(1);
dd($model->sauced_by_current_user);
In normal way i can able to define an object through out the application by defining a service factory in configuration file global.php
i can get the object in controller by just calling $this->getServiceLocator()->get('mycollection')
My code as follows:
In global.php
service_manager' => array(
'factories' => array(
'mycollection'=> function($sm){
$collectionAdapter = new Collection();
$collectionAdapter->addItem("testvalue",'test');
return $collectionAdapter;
}
By adding in global file i can able to retrieve
//`var_dump($this->getServiceLocator()->get('mycollection')->getItem("test"));// will return testvalue`
through out the application
But my issue is that i dont know how to accomplish set values to the service from a controller
My requirement is that i need to set the service in one controller and retrieve in another module
i tried the following code in my IndexController album module
$this->getServiceLocator()->get('mycollection')->addItem('testvalue28','test8');
and in another module student IndexController called
//var_dump($this->getServiceLocator()->get('mycollection')->getItem("test8"));//
How can i accomplish the same which i set in global.php in a controller . or more clearly i need to store the collection values to the entire application at one instance in all modules
Edited
1)The function addItem will be set only based on controller action
2)Is there any thing similar to ZEND_REGISTERY where i can set a value form a particular request and retrieve in another action
//An application controller is define where i need to set different key value pair
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController
{
public function authenticateAction()
{
//----other code---------
var_dump($this->getServiceLocator()->get('mycollection')->addItem('userauthenticationobj','userkey'));
//$redirect=module=user controller action =index
return $this->redirect()->toRoute($redirect);
}
}
In user module index action i need to get the key value pair which is set inside application module in index action. i know this can be done using a session or db or cookies but i want to implement this using a singleton instance through out the application. i don't know to define the correct term in oops so defining the situation
//User controller
namespace User\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController
{
public function authenticateAction()
{
//----other code---------
var_dump($this->getServiceLocator()->get('mycollection')->addItem('userkey'));
//$redirect=module=user controller action =index
return $this->redirect()->toRoute($redirect);
}
}
//IN GLOBAL.PHP i defined
return array(
'service_manager' => array(
'factories' => array(
'mycollection'=> function($sm){
$collectionAdapter = new Collection();
$collectionAdapter->addItem("testvalue",'test');
return $collectionAdapter;
}
),
),
);
//user defined collection reference: http://www.sitepoint.com/collection-classes-in-php/
namespace Application\Adapter;
class Collection
{
private $items = array();
public function addItem($obj, $key = null)
{
if ($key == null)
{
$this->items[] = $obj;
}
else {
if (isset($this->items[$key]))
{
throw new \Exception("Key $key already in use.");
}
else
{
$this->items[$key] = $obj;
}
}
}
public function getItem($key)
{
if (isset($this->items[$key]))
{
return $this->items[$key];
}
else
{
throw new \Exception("Invalid key $key.");
}
}
}
If you are in the same request (this is, the user is not following links, or the page is not being refreshed) the service manager will keep the Collection alive and what you are trying should work.
But if you are redirecting the user to another controller/action, or the user has followed a link, submited a form, or whatever that causes a new page to be loaded, all the values created in the previous page wont exists anymore. If you need to persist them, you should use sessions, cookies, database, etc.
If the values are not set during the action, i.e you dont need a controller to be loaded, but you need all the controllers to be able to add values to the collection on the application bootstrap, no matter what controller is actually loaded, you can add some code to every module, in Module.php onbootstrap function. for instance, in every module's Module.pho, you do:
public function onBootstrap(MvcEvent $e) {
$sm = $e->getApplication ()->getServiceManager ();
$collection = $sm->get('mycollection');
$collection->addItem('testvalue_N','test_N');
}
and then, in every controller/action that is executed, you will have the collection with all the items added by all the modules
I want to use this code in my application:
class ControllerExtension extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{
public function render($view, array $parameters = array(), Response $response = null)
{
//etc.
}
}
But where do I put it and how do i activate it? I'm guessing it's something to do with the services.yml file. I've used Event Listeners, but this is obviously different.
From your code snippet (http://justpaste.it/2caz), it seems that you missed the "return" keyword in your call to parent.
class ControllerExtension extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{
public function render($view, array $parameters = array(), Response $response = null)
{
if($this->getRequest()->getRequestFormat() == 'json') {
return new Response(json_encode($parameters));
} else {
// Missing 'return' in your snippet
return parent::render($view, $parameters, $response);
}
}
}
class MyController extends ControllerExtension
{
public function indexAction()
{
// This should now work
return $this->render(...);
}
}
You can put it in your bundle's Controller directory i.e src/YourNamespace/YourBundleName/Controller/ControllerExtension.php.
Make sure you provide the appropriate namespace in that file:
namespace YourNamespace\YourBundleName\Controller;
class ControllerExtension extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{
...
To use it, either create a route for it in src/YourNamespace/YourBundlename/Resources/config/routing.yml
or
extend it:
namespace YourNamespace\YourBundleName\Controller;
class OtherController extends ControllerExtension
{
...
If what you are actually looking to do is override another bundle's controller, see the cookbook which describes overriding controllers.
Edit:
As far as I know, there's no way to automatically make this controller somehow take effect. You can have each of your controllers extend it as I've indicated above.
You might be able to create an event listener and use the response event to somehow change the response if the format is json. But, I'm not sure how you would access the view data from the event listener.