laravel model event static call issue - events

Why my static method don´t work with a varible class?
/**
* Events
*/
public static function boot() {
parent::boot();
$class = get_called_class(); // The value os $class is: Product ( string)
// This work
Product::creating(function($model) {
return $model->validate();
});
// Don´t work, the closure never called!
$class::creating(function($model) {
return $model->validate();
});
$class::updating(function($model) {
return $model->validate(true);
});
}
Incredible, this work to:
$class = "Product"; //get_called_class();
Solution ( Not elegant , but ... )
On my Product model i put this, to share class name with Base model.
public static function boot() {
parent::$cls = "Product";
parent::boot();
}
but updating does not work yet!

Actually (since PHP 5.3) it should work to do:
$class = 'Product';
$class::creating(function($model){
return $model->validate();
});
But why not just use static:: to access the current class? This way you don't need get_called_class:
static::creating(function($model){
return $model->validate();
});
There's also call_user_func if everything else fails...

Related

Write unit test for trait in model laravel

I've written a trait for auto adding some closure model events to model which use this trait.
This is my code:
trait WatchCardListChange
{
public static function booted()
{
static::watchCardListChange();
}
protected static function watchCardListChange()
{
$modelClass = get_called_class();
$classParts = explode('\\', $modelClass);
$className = end($classParts);
$method = 'addEventsTo' . $className . 'Model';
forward_static_call([$modelClass, $method]);
}
private static function addEventsToAcCardModel()
{
static::created(function ($model) {
CardListChanged::dispatch($model);
});
static::updated(function ($model) {
CardListChanged::dispatch($model);
});
static::deleted(function ($model) {
CardListChanged::dispatch($model);
});
}
}
I've searched for writing unit test for this test which use getMockForTrait and this is my code:
public function test_trait()
{
// First approach
$mock = $this->partialMock(AcCard::class, function (MockInterface $mock) {
$mock->shouldAllowMockingProtectedMethods()->shouldReceive('booted')->once();
});
app()->instance(AcCard::class, $mock);
$model = new AcCard();
// Second approach
$trait = $this->getMockForTrait(WatchCardListChange::class, [], '', true, true, true, ['watchCardListChange']);
$trait->expects(self::exactly(1))->method('watchCardListChange');
// Test
$model = AcCard::factory()->create();
}
But both of 2 approaches seem not work.
I wonder what is the best practice to test this trait? Can someone help?
Try using mocking framework Mockery; the technique is overloading a class /methods:
use Mockery as m;
...
m::mock('overload:Full\Namespace\To\WatchCardListChange', function ($mock) {
$mock->shouldReceive('watchCardListChange')
->once()
->andReturn(whatever);
})->shouldIgnoreMissing();
This will go ahead a create a mock in-place; now you can test static call however you want; You can even mock something inside the function itself and when it being called mock object will returned value will be replaced in-place.

Laravel - Instantiate object and keep it within all controller's methods

I'm working with this case where I need to instantiate an object after a form is submitted in a controller. Everything's working fine until I call this object (as a property) from another method. It appears to be null.
If I intentiate the object from constructor method, I have no problem at all.
I can't keep this object in session because of closure.
Here's what i got so far.
// Version with the object iniate within the constructor that's working
class SearchConsoleController extends Controller
{
private $console;
protected function __construct() {
$callback = route('searchconsole.callback') ;
$this->console = $this->setConsole(env('CLIENT_ID'), env('CLIENT_SECRET'), $callback);
}
private function setConsole($cliendId, $cliendSecret, $callback){
$console = new Console(new Google_Client(), $cliendId, $cliendSecret, $callback);
return $console;
}
public function index(Request $request) {
return view('searchconsole.index')->with('authUrl', $this->console->getAuthUrl());
}
public function callback(Request $request){
if ($request->has('code')) {
$this->console->acceptCode($request->get('code'));
return redirect()->action('SearchConsoleController#listSites', [$request]);
}
else{
die('error');
}
}
Now the version which i'm stucked wih
class SearchConsoleController extends Controller
{
private $console;
private $callback;
protected function __construct() {
$this->callback = route('searchconsole.callback') ;
}
private function setConsole($cliendId, $cliendSecret, $callback){
$console = new Console(new Google_Client(), $cliendId, $cliendSecret, $this->callback);
return $console;
}
public function index(Request $request) {
// VIEW WITH A FORM FROM WHICH I GET CLIENT_SECRET & CLIENT_ID var
return view('searchconsole.index');
}
public function getAuthUrl(Request $request) {
// FORM FROM INDEX IS SUBMITTED
$clientId = ($request->has('google-client-id')) ?
$request->get('google-client-id') :
null
;
$clientSecret = ($request->has('google-client-secret')) ?
$request->get('google-client-secret') :
null
;
$this->console = $this->setConsole($clientId, $clientSecret, $this->callback);
return $this->console->getAuthUrl();
}
public function callback(Request $request){
if ($request->has('code')) {
// ***** MY PROBLEM *********
$this->console->acceptCode($request->get('code')); // HERE $this->console IS NULL;
// *******************
return redirect()->action('SearchConsoleController#listSites', [$request]);
}
else{
die('error');
}
}
I just can't figure out how I can do this so console is still available
UPDATE :
following #iamab.in advice, i looked into Service Provider but i just dont know how i can instante the Console Object within the service provider.
Here's what i've done.
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Helpers\Console;
use Google_Client;
use Illuminate\Support\Facades\Route;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(Console::class, function() {
$request = app(\Illuminate\Http\Request::class);
$clientId = ($request->has('google-client-id')) ?
$request->get('google-client-id') :
null
;
$clientSecret = ($request->has('google-client-secret')) ?
$request->get('google-client-secret') :
null
;
$callback = Route::get()->name('searchconsole.callback');
return new Console(new Google_Client(), $clientId, $clientSecret, $callback);
});
}
public function boot(){}
....
I just dont know how and where to implement it.
Thanks again
Update#2 :
okay my solution was working, I just didnt launch the correct app ..... 😅

Get Language from construct in laravel

i'm trying to get selected language in my construct to use in any function in that class:
my route:
Route::group(['prefix' => 'admin', 'middleware' => ['AdminMiddleWare','auth','localization']], function(){
Route::get('/', 'AdminController#index')->name('admin.index');
});
My Middleware:
public function handle($request, Closure $next)
{
if (Session::has('locale') AND array_key_exists(Session::get('locale'), Config::get('languages'))) {
App::setLocale(Session::get('locale'));
}
else {
App::setLocale(Config::get('app.locale'));
}
return $next($request);
}
My controller :
public $lang;
public function __construct()
{
$this->lang = Language::where('lang','=',app()->getLocale())->first();
}
public function index()
{
$lang = $this->lang;
return $lang;
}
but i'm getting only the default locale;
but if i change the controller to this:
public function index()
{
$lang = Language::where('lang','=',app()->getLocale())->first();
return $lang;
}
it will work...
how to get in construct and use it in all functions??
In Laravel, a controller is instantiated before middleware has run. Your controller's constructor is making the query before the middleware has had a chance to check and store the locale value.
There are multiple ways you can set up to work around this - the important thing is to make the call after middleware runs. One way is to use a getter method on your controller:
class Controller
{
/**
* #var Language
*/
private $lang;
public function index()
{
$lang = $this->getLang();
// ...
}
private function getLang()
{
if ($this->lang) {
return $this->lang;
}
return $this->lang = Language::where('lang','=',app()->getLocale())->first();
}
}

Laravel eloquent save

I am trying to change a value on save
public static function boot()
{
static::saving(function ($formRow) {
$formRow->sales = 1;
});
}
But the weird part is that its not changing the sales to 1, any idea why ?
You're missing the call to the parent's boot method:
public static function boot()
{
// add this
parent::boot();
static::saving(function ($formRow) {
$formRow->sales = 1;
});
}
You can just override the save method on the model itself:
public function save(array $options = [])

Using Doctrine in Joomla 1.5

I'm trying to use Doctrine in Joomla 1.5 but have not been able to get anything running.
According to this article: http://magazine.joomla.org/issues/issue-may-2011/item/447-using-doctrine-ORM-in-joomla
I immediately get Fatal error: Class 'Fatal error: Call to undefined method JController::getInstance() in /var/www/html/hosts/joomla/public_html/components/com_bugs/bugs.php on line 13
The bugs.php looks like this:
// no direct access
defined('_JEXEC') or die; // Include dependancies
jimport('joomla.application.component.controller');
//require_once(JPATH_LIBRARIES . '/doctrine/vendor/autoload.php');
require_once(JPATH_LIBRARIES . '/doctrine/bootstrap.php');
require_once(JPATH_LIBRARIES . '/doctrine/JoomlaDoctrineBootstrapper.php');
require_once(JPATH_COMPONENT.DS.'controller.php');
//$controller = new BugsController(JRequest::getVar('task', ''));
$controller = JController::getInstance('Bugs');
Not sure how to implement this, when trying to use the $controller = new BugsController the error is: Fatal error: Class 'JController' not found in
This because I have the autoload on in bugs.php and have /public_html/components/com_bugs/controller.php extend /public_html/libraries/doctrine/JoomlaDoctrineBootstrapper.php the JoomlaDoctrineBootstrapper exends JController but JController cannot be found anymore after composer and the autoload did something.
I'm starting to think that it's not possible to use Joomla with Doctrine since Doctrine has to be installed with composer (didn't find any other documentation on how to download and configure it) and composer seems to want everything in vendor so have to put all the Joomla classes in vendor too?
[UPDATE]
It looks like whatever composer does in /public_html/libraries/doctrine/vendor/autoload.php completely breaks jimport('joomla.application.component.controller')
Not including the autoload however gives me another problem, like none of the Doctrine classes are found: Class 'Doctrine\Common\Cache\ArrayCache' not found
Maybe I'll try and hack /public_html/libraries/doctrine/vendor/composer/autoload_real.php to try and see if that one can load Joomla classes for me.
Either jimport or composer won't work because jimport defines __autoload. Instead of __autoload I'm using spl_autoload_register that only seems to work with PHP version starting from 5.1.2.
Changed loader:
/public_html/libraries/loader.php
class JLoader
{
public static function autoload($class)
{
if(JLoader::load($class)) {
return true;
}
return false;
}
//... other code and comments
function import( $filePath, $base = null, $key = 'libraries.' )
{
static $paths;
if (!isset($paths)) {
$paths = array();
//assuming PHP 5 >= 5.1.2
spl_autoload_register(array('JLoader', 'autoload'), true, true);
}
//remove the __autoload function
The bugs.php looks like this:
/public_html/components/com_bugs/bugs.php
<?php
// no direct access
defined('_JEXEC') or die; // Include dependancies
require_once(JPATH_LIBRARIES . '/doctrine/vendor/autoload.php');
require_once(JPATH_LIBRARIES . '/doctrine/bootstrap.php');
require_once(JPATH_LIBRARIES . '/doctrine/JoomlaDoctrineBootstrapper.php');
require_once(JPATH_COMPONENT.DS.'controller.php');
//using links like /index.php?option=com_bugs&format=text&task=save
// defaults to link so above is same as: http://joomla/index.php?option=com_bugs&format=text&task=save&router=link
$route=JRequest::getVar('router', 'Link');
$controllerName = 'bugsController'.$route;
//include the controller
include_once(dirname(__FILE__) . '/controllers/'.$route.".php");
$controller = new bugsControllerlink(JRequest::getVar('task', ''));
$controller->setEntityManager(bootstrapDoctrine());
$controller->execute(JRequest::getVar('task', ''));
$controller->redirect();
/**
* Initialize doctrine by setting the entities and proxies locaties. Also define
* a default namespace for the proxies.
*/
function bootstrapDoctrine() {
$doctrineProxy = new JoomlaDoctrineBootstrapper(JoomlaDoctrineBootstrapper::APP_MODE_DEVELOPMENT);
$doctrineProxy->setEntityLibrary(dirname(__FILE__) . '/models');
$doctrineProxy->setProxyLibrary(dirname(__FILE__) . '/proxies');
$doctrineProxy->setProxyNamespace('Joomla\Proxies');
$doctrineProxy->setConnectionOptions(getConfigurationOptions());
$doctrineProxy->bootstrap();
return $doctrineProxy->getEntityManager();
}
function getConfigurationOptions() { // Define database configuration options
$joomlaConfig = JFactory::getConfig();
return array('driver' => 'pdo_mysql', 'path' => 'database.mysql'
, 'dbname' => $joomlaConfig->getValue("config.data.db")
, 'user' => $joomlaConfig->getValue("config.data.user")
, 'password' => $joomlaConfig->getValue("config.data.password"));
}
?>
The link controller looks like: (file name has start with a capital L)
/public_html/components/com_bugs/controllers/Link.php
<?php
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
// import Joomla controller library (done by composer)
//jimport('joomla.application.component.controller');
class bugsControllerlink extends JoomlaDoctrineBootstrapper{
function __construct()
{
parent::__construct();
$this->registerTask( 'show','show' );
$this->registerTask( 'save','save' );
}
function save(){
$model=$this->getModel('link');
$view = $this->getView('show','text');
$view->setLayout('save');
$model->em=$this->getEntityManager();
$view->data = $model->save();
$view->display();
}
}
Didn't change the boodtrap, that still looks like this:
/public_html/libraries/doctrine/bootstrap.php
<?php
interface JoomlaDoctrineController {
public function setEntityManager(Doctrine\ORM\EntityManager $entityManager);
}
?>
The /public_html/libraries/doctrine/JoomlaDoctrineBootstrapper.php looks like:
<?php
/** * Configuration class to integrate Doctrine into Joomla. *
* #author pderaaij <removed email, check link in question> */
use Composer\Autoload\ClassLoader,
Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration,
Doctrine\Common\Cache\ArrayCache;
jimport( 'joomla.application.component.controller' );
class JoomlaDoctrineBootstrapper extends JController{
const APP_MODE_DEVELOPMENT = 1;
const APP_MODE_PRODUCTION = 2;
private $applicationMode;
private $cache;
private $entityLibrary;
private $proxyLibrary;
private $proxyNamespace;
private $entityManager;
private $connectionOptions;
public function __construct($applicationMode=1) {
$this->applicationMode = $applicationMode;
$this->_name="bugs";
parent::__construct();
}
public function getConnectionOptions() {
return $this->connectionOptions;
}
public function setConnectionOptions($connectionOptions) {
$this->connectionOptions = $connectionOptions;
}
public function getProxyLibrary() {
return $this->proxyLibrary;
}
public function setProxyLibrary($proxyLibrary) {
$this->proxyLibrary = $proxyLibrary;
}
public function getProxyNamespace() {
return $this->proxyNamespace;
}
public function setProxyNamespace($proxyNamespace) {
$this->proxyNamespace = $proxyNamespace;
}
public function getCache() {
return $this->cache;
}
public function setCache($cache) {
$this->cache = $cache;
}
public function getEntityLibrary() {
return $this->entityLibrary;
}
public function setEntityLibrary($entityLibrary) {
$this->entityLibrary = $entityLibrary;
}
public function getApplicationMode() {
return $this->applicationMode;
}
public function setApplicationMode($applicationMode) {
$this->applicationMode = $applicationMode;
}
public function getEntityManager() {
return $this->entityManager;
}
public function setEntityManager($entityManager) {
$this->entityManager = $entityManager;
}
/** * Bootstrap Doctrine, setting the libraries and namespaces and creating * the entitymanager */
public function bootstrap() {
$this->registerClassLoader(); // Load cache
if ($this->getApplicationMode() == self::APP_MODE_DEVELOPMENT) {
$this->cache = new ArrayCache;
} else {
$this->cache = new ApcCache;
} /** #var $config Doctrine\ORM\Configuration */ $config = new Configuration;
$config->setMetadataCacheImpl($this->cache);
$driverImpl = $config->newDefaultAnnotationDriver($this->getEntityLibrary());
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($this->cache);
$config->setProxyDir($this->getProxyLibrary());
$config->setProxyNamespace($this->getProxyNamespace());
if ($this->applicationMode == self::APP_MODE_DEVELOPMENT) {
$config->setAutoGenerateProxyClasses(true);
} else {
$config->setAutoGenerateProxyClasses(false);
} $this->entityManager = EntityManager::create($this->getConnectionOptions(), $config);
}
/** * Register the different classloaders for each type. */
private function registerClassLoader() { // Autoloader for all the Doctrine library files
//Doctrine was done by public_html/libraries/doctrine/vendor/autoload.php
// $classLoader = new ClassLoader('Doctrine', dirname(__FILE__) . '/');
// $classLoader->register(); // Autoloader for all Entities
//name of ComposerAutoloader is defined in /public_html/libraries/doctrine/vendor/composer/autoload_real.php
$modelLoader = ComposerAutoloaderInit825f56ea1383e6b7fef7ea99c51fea36::getLoader();
$modelLoader->set("Entities\\",dirname(__FILE__)."/../../components/com_"
//not sure how to do the proxies yet, have to check this with production settings
// $proxiesClassLoader = new ClassLoader('Proxies', $this->getProxyLibrary());
// $proxiesClassLoader->register();
}
}
?>
The Joomla model save function looks something like this (checking received JSON should be done in a controller or helper function):
public function save() {
//a textbox having the name 'json' or xhr post
$link = JRequest::getVar('json',false,'post');
if($link==false){
return;
}
$link = json_decode($link);
$newLink = new Link();
$newLink->setId($link->id);
$newLink->setName($link->name);
foreach($link->categories as $category){
$cat = new Category();
$cat->setId($category->id);
$cat->setName($category->name);
$newLink->addCategorie($cat);
}
$this->em->persist($newLink);
$this->em->flush();
return $link;
}
I guess the code as is will break when using APP_MODE_PRODUCTION in /public_html/components/com_bugs/bugs.php

Resources