I have a problem described in subject. Let me explain. I use basic application template. I have simple model Search, which uses application component GoogleCustomSearch for search through Google Custom Search API. GoogleCustomSearch has to be configured with Google API key and Search Engine ID. I specifying Google API key and Search Engine ID via application config config/web.php. I would like to inject configured instance of GoogleCustomSearch into an instance of the Search model. Is it possible to reach my goal in a cleaner way (workaround below)?
File: models/Search.php
namespace app\models;
use app\components\GoogleCustomSearch;
use yii\base\Model;
class Search extends Model
{
/** #var GoogleCustomSearch */
protected $googleCustomSearch;
public function __construct(GoogleCustomSearch $googleCustomSearch, array $config = [])
{
$this->googleCustomSearch = $googleCustomSearch;
parent::__construct($config);
}
....
}
File: components/GoogleCustomSearch.php
namespace app\components;
use yii\base\Component;
use yii\base\InvalidValueException;
class GoogleCustomSearch extends Component
{
public $searchEngineId;
public $apiKey;
...
}
My current workaround below
File: config/bootstrap.php
use app\models\Search;
use yii\di\Container;
use yii\web\Application;
\Yii::$container->set('search', function (Container $container, $params, $config) {
$googleCustomSearch = $container->get(Application::class)->googleCustomSearch;
array_unshift($params, $googleCustomSearch);
return $container->get(Search::class, $params, $config);
});
File: web/index.php
use yii\web\Application;
....
require (__DIR__ . '/../config/bootstrap.php');
$config = require(__DIR__ . '/../config/web.php');
\Yii::$container
->setSingleton(Application::class, [], [$config])
->get(Application::class)
->run()
;
And then call
/** #var Search $model */
$model = \Yii::createObject('search');
Is there cleaner way to inject configured component to an object instance?
Your model part is good. It knows nothing about how dependency is injected and that's correct way of doing it.
In the bootstrap I'd set GoogleCustomSearch instead of search model:
Yii::$container->set(GoogleCustomSearch::class, function (Container $container, $params, $config) {
return Yii::$app->googleCustomSearch;
});
Not sure about the purpose of modified index.php.
Related
Since the last 4 hours I'm trying to understand the logic of Symfony 2 services and how they integrate in the application...
Basically I'm trying to set my EntityManager via a service and use it in a controller
I have the following structure
Bundle1/Controller/Bundle1Controller.php
Bundle1/Services/EntityService.php
Bundle2/Controller/Bundle2Controller.php
Bundle3/Controller/Bundle3Controller.php
....
I'm trying to make a REST API with different entry points, that's why I use multiple bundles bundle2,bundle3....
The logic is the following:
A POST is fired to Bundle2/Controller/Bundle2Controller.php
Bundle2Controller.php instances a new() Bundle1Controller.php
Inside Bundle1Controller I want to access a service entity_service in order to get my EntityManager
I have 2 cases in which I manage to land...
In Bundle1/Controller/Bundle1Controller if I try $this->container or $this->get('entity_service') I get a null everytime
If I set the container in Bundle2/Controller/Bundle2Controller and try $this->get('entity_service') I get You have requested a non-existent service "entity_service"
I will place all the code below
Bundle1/Controller/Bundle1Controller
<?php
namespace Bundle1\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use EntityBundle\Entity\TestEntity;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
class Bundle1Controller extends Controller
{
/**
* #param $response
* #return array
*/
public function verifyWebHookRespone($response){
$em = $this->get('entity_service')->getEm();
$array = json_decode($response);
$mapping = $em->getRepository('EntityBundle:TestEntity')
->findBy(["phone" => $array['usernumber']]);
return $mapping;
}
}
Bundle2/Controller/Bundle2Controller.php
<?php
namespace Bundle2\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Bundle1\Controller\Bundle1Controller;
class Bundle2Controller extends Controller
{
public function webhookAction(Request $request)
{
$data = $request->request->get('messages');
$model = new Bundle1Controller();
$responseMessage = $model->verifyWebHookRespone($data);
return new Response($responseMessage, Response::HTTP_CREATED, ['X-My-Header' => 'My Value']);
}
}
Bundle1/Services/EntityService.php
<?php
namespace EntityBundle\Services;
use Doctrine\ORM\EntityManager;
use Symfony\Component\DependencyInjection\Container;
class EntityService
{
protected $em;
private $container;
public function __construct(EntityManager $entityManager, Container $container)
{
$this->em = $entityManager;
$this->container = $container;
}
/**
* #return EntityManager
*/
public function getEm()
{
return $this->em;
}
}
services.yml
services:
entity_service:
class: Bundle1\Services\EntityService
arguments: [ "#doctrine.orm.entity_manager" , "#service_container" ]
Can anyone please help me with something regarding this issue?
How can I register a service and call it from anywhere no matter the bundle or another service?
You should check where your services.yml is located and whether it is imported in the config.yml
You can't just instantiate a controller and expect it to work, you need to set the container.
But you can call EntityManager without needing any other service by using;
$this->get('doctrine.orm.entity_manager');
I can't understand your structure or what you are trying to achieve, but those are the options to go about if you want to keep this structure.
I want information about the system locale to be available in every view, so I could highlight whatever language is currently selected by a user. After some googling around, I've found the value-sharing issue addressed in the official documentation. However, after putting the code into boot() like this:
class AppServiceProvider extends ServiceProvider{
public function boot(){
view()->share('locale', \Lang::getLocale());
}
}
the $locale variable, when accessed in views, always holds the default system locale, not the currently selected one. Why?
I usually use View Composers so it's more clear and readable.
For example If I want to share a variable with the main navbar to all of my views I follow the below rules:
1. Create new service provider
You can create a service provider with artisan cli:
php artisan make:provider ViewComposerServiceProvider
In the ViewComposerServiceProvider file create composeNavigation method in which has the blade template main.nav-menu that represents the navmenu with shared variables.
The ViewComposerServiceProvider looks like:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ViewComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
$this->composeNavigation();
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
private function composeNavigation()
{
view()->composer('main.nav-menu', 'App\Http\ViewComposers\NavComposer');
}
}
2. Create Composer
As you saw in the file above we have App\Http\ViewComposers\NavComposer.php so let's create that file. Create the folder ViewComposers in the App\Http and then inside create NavComposer.php file.
The NavComposer.php file:
<?php
namespace App\Http\ViewComposers;
use App\Repositories\NavMenuRepository;
use Illuminate\View\View;
class NavComposer
{
protected $menu;
public function __construct(NavMenuRepository $menu)
{
$this->menu = $menu;
}
public function compose(View $view)
{
$thing= $this->menu->thing();
$somethingElse = $this->menu->somethingElseForMyDatabase();
$view->with(compact('thing', 'somethingElse'));
}
}
3. Create repository
As you saw above in the NavComposer.php file we have repository. Usually, I create a repository in the App directory, so create Repositories directory in the App and then, create inside NavMenuRepository.php file.
This file is the heart of that design pattern. In that file we have to take the value of our variables that we want to share with all of our views.
Take a look at the file bellow:
<?php
namespace App\Repositories;
use App\Thing;
use DB;
class NavMenuRepository
{
public function thing()
{
$getVarForShareWithAllViews = Thing::where('name','something')->firstOrFail();
return $getVarForShareWithAllViews;
}
public function somethingElseForMyDatabase()
{
$getSomethingToMyViews = DB::table('table')->select('name', 'something')->get();
return $getSomethingToMyViews;
}
}
For people with small project:
Firstly, The accepted answer is awesome!
For Laravel 5.2 users:
Just use the new blade directive #inject within your views like this
#inject('shared','App\Utilities\SharedWithView')
then you can use it:
{{ $shared->functionName() }}
And SharedWithView is a simple class like this one:
namespace App\Utilities;
use App\Repositories\SomeRepositoryLikeArticlesRepository;
class SharedWithView {
public function functionName() {
$properNameHere = new SomeRepositoryLikeArticlesRepository();
return $properNameHere->forEaxmpleGetMostViewedArticles( 10 );
}
}
I want information about the system locale to be available in every view, so I could highlight whatever language is currently selected by a user. After some googling around, I've found the value-sharing issue addressed in the official documentation. However, after putting the code into boot() like this:
class AppServiceProvider extends ServiceProvider{
public function boot(){
view()->share('locale', \Lang::getLocale());
}
}
the $locale variable, when accessed in views, always holds the default system locale, not the currently selected one. Why?
I usually use View Composers so it's more clear and readable.
For example If I want to share a variable with the main navbar to all of my views I follow the below rules:
1. Create new service provider
You can create a service provider with artisan cli:
php artisan make:provider ViewComposerServiceProvider
In the ViewComposerServiceProvider file create composeNavigation method in which has the blade template main.nav-menu that represents the navmenu with shared variables.
The ViewComposerServiceProvider looks like:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ViewComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
$this->composeNavigation();
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
private function composeNavigation()
{
view()->composer('main.nav-menu', 'App\Http\ViewComposers\NavComposer');
}
}
2. Create Composer
As you saw in the file above we have App\Http\ViewComposers\NavComposer.php so let's create that file. Create the folder ViewComposers in the App\Http and then inside create NavComposer.php file.
The NavComposer.php file:
<?php
namespace App\Http\ViewComposers;
use App\Repositories\NavMenuRepository;
use Illuminate\View\View;
class NavComposer
{
protected $menu;
public function __construct(NavMenuRepository $menu)
{
$this->menu = $menu;
}
public function compose(View $view)
{
$thing= $this->menu->thing();
$somethingElse = $this->menu->somethingElseForMyDatabase();
$view->with(compact('thing', 'somethingElse'));
}
}
3. Create repository
As you saw above in the NavComposer.php file we have repository. Usually, I create a repository in the App directory, so create Repositories directory in the App and then, create inside NavMenuRepository.php file.
This file is the heart of that design pattern. In that file we have to take the value of our variables that we want to share with all of our views.
Take a look at the file bellow:
<?php
namespace App\Repositories;
use App\Thing;
use DB;
class NavMenuRepository
{
public function thing()
{
$getVarForShareWithAllViews = Thing::where('name','something')->firstOrFail();
return $getVarForShareWithAllViews;
}
public function somethingElseForMyDatabase()
{
$getSomethingToMyViews = DB::table('table')->select('name', 'something')->get();
return $getSomethingToMyViews;
}
}
For people with small project:
Firstly, The accepted answer is awesome!
For Laravel 5.2 users:
Just use the new blade directive #inject within your views like this
#inject('shared','App\Utilities\SharedWithView')
then you can use it:
{{ $shared->functionName() }}
And SharedWithView is a simple class like this one:
namespace App\Utilities;
use App\Repositories\SomeRepositoryLikeArticlesRepository;
class SharedWithView {
public function functionName() {
$properNameHere = new SomeRepositoryLikeArticlesRepository();
return $properNameHere->forEaxmpleGetMostViewedArticles( 10 );
}
}
I'm building an application, now i'm created a helper
class Students{
public static function return_student_names()
{
$_only_student_first_name = array('a','b','c');
return $_only_student_first_name;
}
}
now i'm unable to do something like this in controller
namespace App\Http\Controllers;
class WelcomeController extends Controller
{
public function index()
{
return view('student/homepage');
}
public function StudentData($first_name = null)
{
/* ********** unable to perform this action *********/
$students = Student::return_student_names();
/* ********** unable to perform this action *********/
}
}
this is my helper service provider
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class HelperServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
foreach(glob(app_path().'/Helpers/*.php') as $filename){
require_once($filename);
}
}
}
i event added it as an alias in config/app.php file
'Student' => App\Helpers\Students::class,
Try putting use App\Helpers\Student; at the top of your controller beneath the namespace delcaration:
namespace App\Http\Controllers;
use App\Helpers\Student;
class WelcomeController extends Controller
{
// ...
Look more into PHP namespaces and how they are used, I believe you may have a deficient understanding about them. Their only purpose is to make so you can name and use two classes with the same name (e.g. App\Helpers\Student vs maybe App\Models\Student). If you needed to use both of those classes inside of the same source file, you can alias one of them like this:
use App\Helpers\Student;
use App\Models\Student as StudentModel;
// Will create an instance of App\Helpers\Student
$student = new Student();
// Will create an instance of App\Models\Student
$student2 = new StudentModel();
You do not need to have a service provider for this, just the normal language features. What you would need a service provider for is if you wanted to defer the construction of your Student object to the IoC:
public function register()
{
$app->bind('App\Helpers\Student', function() {
return new \App\Helpers\Student;
});
}
// ...
$student = app()->make('App\Helpers\Student');
You should never have to include or require a class file in laravel because that is one of the functions that composer provides.
You do not need a service provider to make it works. Just lets the Students class as you did:
class Students{
public static function return_student_names()
{
$_only_student_first_name = array('a','b','c');
return $_only_student_first_name;
}
}
all its methods should be static
You added the Facade correctly:
'Student' => App\Helpers\Students::class,
Finally, looks like your problem is caused by forgetting a backslash at facade name. Uses \Students instead of Students:
public function StudentData($first_name = null)
{
$students = \Student::return_student_names();
}
When using a facade, it is not necessary makes nay include, the facades were made to avoid complex includes in everywhere.
In using the laravel framework, how can I call a function defined in base_controller, in a view. For exacmple:
class Base_Controller extends Controller {
public static function format_something()
{
return something;
}
}
How can i call format_something() in a view file?
Usually the error I get looks something like this:
Method [link_to_action] is not defined on the View class.
Probably a silly question, but thanks in advance!
Edit
Okay! First the correct place to do something like this is in the libraries folder.
Second, problem is that your class cannot have underscores.
So in application/libraries I made file AppHelper.php with class
class AppHelper {
public static function format_something()
{
return something;
}
}
And can call it like:
$formated = AppHelper::format_something;
Thanks for the help and the good forum find Boofus McGoofus.
For me is working:
Create directory "helpers" or whatever and file:
// app/helpers/AppHelper.php
class AppHelper {
public static function format_something()
{
return something;
}
}
Add path to composer.json
// composer.json
"autoload": {
"classmap": [
"app/helpers" // <-------- add this line
]
},
Run: (reload the autoload)
composer dump-autoload
Now you can call:
$formated = AppHelper::format_something();
This answer was written for Laravel 3. For Laravel 4 and after, Lajdák Marek's answer using Composer's autoloader is better.
Functions like format_something() don't belong in the controller. The controller should just be about collecting data from various sources and passing it to the view. It's job is mostly just routing.
I've created a folder called "helpers" in the application folder for all my little helpery functions. To make sure all my controllers, views, and models have access to them, I've included the following in my start.php file:
foreach(glob(path('app').'helpers/*.php') as $filename) {
include $filename;
}
I suspect that there's a better way to do that, but so far it has worked for me.
You can inspire yourself from Laravel framework itself.
I will take your example of a formatter and refer to url helper in Laravel Framework.
Start by creating your own helpers.php file:
<?php
if (! function_exists('format_that')) {
/**
* Generate something
*
* #param string $text
* #return string
*/
function format_that($text)
{
return app('formatter')->format_that($text);
}
}
And add it to your composer.json file:
"autoload": {
"files": [
"app/helpers/helpers.php"
]
}
Run this command to recreate the autoload php file:
$ composer dumpautoload
Create your service provider app/Providers/FormatterServiceProvider.php:
<?php
namespace Illuminate\Routing;
use Illuminate\Support\ServiceProvider;
use App\Helpers\FormatGenerator;
class FormatterServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app['formatter'] = $this->app->share(function ($app) {
return new FormatGenerator($app['request']);
});
}
}
Register your service provider. Laravel framework invokes register method but you only need to add it to your app config file config/app.php:
'providers' => [
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
// other providers...
App\Providers\FormatterServiceProvider::class,
]
Finally, create your actual generator class app/Helpers/FormatGenerator.php
<?php
namespace App\Helpers;
use Illuminate\Http\Request;
class FormatGenerator
{
protected $request;
/**
* Create a new URL Generator instance.
*
* #param \Illuminate\Routing\RouteCollection $routes
* #param \Illuminate\Http\Request $request
* #return void
*/
public function __construct(Request $request)
{
$this->request = $request;
}
public function format_that($text){
if ($request->path() == "home"){
return mb_strtoupper($text);
}
else{
return $text;
}
}
}
You can optionally create a Facade app/Facade/Formatter.php, to be able to do Formatter::format_that($text):
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
/**
* #see \App\Helpers\FormatGenerator
*/
class Formatter extends Facade
{
protected static function getFacadeAccessor() { return 'formatter'; }
}
You could ask yourself:
Why the facade? You can reuse the component somewhere else by simply calling Formatter::format_that($text) instead of app('formatter')->format_that($text). Sugar syntax really.
Why the Service provider? Dependence injections. If you need to use Request or want to build a complex object, the Service provider will take care of that for you and make it available in your $app object.