CodeIgniter: MVC and Widgets? - model-view-controller

I'm new to codeigniter and building web applications using MVC. I'm trying to wrap my head around how I would implement widgets in a modular fashion in my application. My question is more theoretical at this point. I don't have actual code to show.
What I want to know is this, how would I construct a data-driven widget in such a way that I can simply drop it on to any page that I want. For example, let's say I have a widget called Widget. I've created a model file called /models/widget_model.php. I then have a controller file called /controllers/widget.php. Obviously my controller will use the model to grab necessary data from my database. What I don't understand is how to use this as a widget dropped onto multiple views. What I'm seeing and understand so far is how to use a controller to drive a specific view. So it's basically like one controller is used per page. What would be the process of using this widget in a modular fashion I guess?

What you search for is HMVC. There are two common library/packages you can use : Modular CI or HMVC. With that, you can actually put something like <?php echo Modules::run('module/controller/method', $param, $...); ?> as a widget, in your view files.

You can do it via drivers. Send the controller as an object reference to the driver to use view class. Then you just load drivers and use them as plugins.
Edit:
Here is the code I use in my application:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* CodeIgniter base widget driver
*
* #author Alex
* #version 1.0.0
*/
class Basedriver {
/**
* Current specified controller.
* #var CI_Controller
*/
public $controller;
/**
* Contents of the driver which should be outputted or returned.
* #var string
*/
protected $contents;
/**
* Loader Class
* #var CI_Loader
*/
protected $load;
/**
* Constructor function for Basedriver class
*/
public function __construct()
{
$this->controller =& get_instance();
$this->load = $this->controller->load;
}
/**
* Renders driver data into specified output. If $echo_contents is true,
* output is echoed to the client, otherwise it is returned.
* #param boolean $echo_contents Specifies whether the content should be outputted or returned as string
* #param mixed $params Array of parameters which should be sent to the driver
* #return string Returned driver data if $echo_contents is set
*/
public function render($params = NULL, $echo_contents = true)
{
$this->parse_params($params);
$this->run();
if ($echo_contents)
echo $this->contents;
else
return $this->contents;
return NULL;
}
/**
* Default run function for all drivers, should be overidden by extending classes.
*/
protected function run()
{
$this->contents = NULL;
}
/**
* Parses parameters and sets them as variables.
* Default variables need to be defined in extending class
*/
protected function parse_params($params)
{
if ($params === NULL) return;
foreach($params as $variable => $value)
{
if (isset($this->$variable))
$this->$variable = $value;
}
}
}
/* End of file Basedriver.php */
/* Location: ./application/libraries/Basedriver.php */
Load class is there to allow you to use view class and controller is there to allow you to use database functions and to give you some other access if you need it. This class needs to be loaded before all other drivers (widgets) and all drivers (widgets) need to extend this class. You can do this by adding 'basedriver' in $config['libraries'] array in application/config/autoload.php.
Example Driver Widget:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Example extends Basedriver
{
protected $parameter1 = 'defaultvalueparam1';
protected $parameter2 = 'defaultvalueparam2';
protected function run()
{
// Widget logic here...
// you can use $this->load->view and $this->controller->db here
$this->contents = 'final_processed_data_here';
}
}
/* End of file Example.php */
/* Location: ./application/libraries/Example/Example.php */
To use the driver which extends Basedriver as a widget, example:
$this->load->driver('example');
$this->example->render(array('parameter1' => '1', 'parameter2' => '2'));

I think you could simply using CI's view system. You create a view per widget, then you inject any variable you want from your model, and finally you display the resulting HTML anywhere you want. I can't think of any particular difficulty.

Related

Laravel/Octane: How to reset route controllers' state

In Laravel v9/Octane/Swoole, I do have private properties in route controllers, e.g.
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
class SignupController extends Controller
{
/** #var ?\App\SignupCode A verification code object */
protected $code;
It looks like the property is "shared" between requests under Octane. I have more controllers like this. How do I make sure the controller state gets reset on every request? I've read the whole Octane documentation a few times, and it's still unclear how to do that.
I solved it by created listener
<?php
namespace App\Listeners;
use Illuminate\Routing\Router;
class ResetControllerState
{
/**
* Handle the event.
*
* #param mixed $event
* #return void
*/
public function handle($event): void
{
/** #var Router $router */
$router = $event->sandbox->make(Router::class);
$currentRoute = $router->current();
if($currentRoute && $currentRoute->controller)
$currentRoute->controller = null;
}
}
and add it to array of listeners in octane config
RequestReceived::class => [
...Octane::prepareApplicationForNextOperation(),
...Octane::prepareApplicationForNextRequest(),
\App\Listeners\ResetControllerState::class
//
],
I do not know what the consequences may be, but so far it works well.

Laravel - extending Illuminate\Http\Request and using session

I've extended the Illuminate\Http\Request class and am passing it along to my controller.
use Illuminate\Http\Request;
class MyRequest extends Request
{
...
}
Controller
class MyController
{
// Doesnt work
public function something(MyRequest $request) {
var_dump($request->session())
}
// Does work
public function something(Illuminate\Http\Request $request) {
var_dump($request->session())
}
}
So when I'm trying to get session $request->session() I get RuntimeException - Session store not set on request.
I feel it has something to do with not running middlewares on my custom request but I dont know how to make it work. Helping or pionting to the right direction would be much apreciated.
To give a little bit more info. I'm trying to make a wizard. Several pages where content of one page depends on choices on previous pages. I'm storing the data in session and on the final page I do "stuff" with it and clear the session storage of current user.
Because it a lot of lines of code and since session instace lives on request I though it would be elegant to hide all those line it in custom request and in controler simply call $myRequest->storeInputs()
This is what seemed to me as "most elegant" in this particular case so I would prefer to finish it this way but I'm also open to a different solution if there is a better aproach.
Summary: basically where should I hide all those lines which are storing and retriving data from sesison?
Solution: I actually solved it by extending FormRequest since it was solution which was the best fit for what I was trying to do. However I accepted the one offered answer since I believe it is generally better solution and I would use it if not for this very particullar case.
The classic Laravel request already got a bunch of settings you didn't catch on your custom request. To achieve that, you should setup a middleware (maybe global in your use-case) which replaces old request in Laravel's container by yours.
<?php
namespace App\Http\Middleware;
use App\Http\MyRequest;
use Closure;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
class CustomizeRequest
{
/**
* #var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* #var \App\Http\MyRequest
*/
protected $myRequest;
/**
* #param \Illuminate\Contracts\Foundation\Application $app
* #param \App\Http\MyRequest $myRequest
*/
public function __construct(Application $app, MyRequest $myRequest)
{
$this->app = $app;
$this->myRequest = $myRequest;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$this->app->instance(
'request', Request::createFrom($request, $this->myRequest)
);
return $next($this->myRequest);
}
}

Middleware before Model injection

I have a design doubt I would like to share.
I have a model in Laravel with an Observer at retrieved:
class MailingObserver
{
public function retrieved($mailing)
{
// we retrieve HTML content from disk file
$mailing->setAttribute('content', \Illuminate\Support\Facades\Storage::disk('mailings')->get("{$mailing->id}-{$mailing->slug}.html"));
$mailing->syncOriginal();
}
}
which retrieve an attribute stored in a plain text instead of database.
The site is a multibrand platform so disk('mailings') is different per each logged user. This configuration is loaded in a middleware according to the the current logged user.
Up to here all is fine.
Now the "problem". I have a Controller which injects the entity Mailing:
class MailingCrudController extends CrudController
{
/**
* Sends the mailing
* #param Request $request
* #param \App\Mailing $mailing
*/
public function send(Request $request, \App\Mailing $mailing)
{
// WHATEVER
}
}
When the model is injected the retrieved Observer method is fired but the Middleware wasn't still executed so mailings disk is still not set up.
I don't know how to change this order: first execute middleare, then the model injection.
One approach
I tried in AppServiceProvider to add:
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$middleware = new \App\Http\Middleware\CheckBrandHost();
$middleware->setBrandInformation(request());
$middleware->loadBrandConfig(request()->get('brand.code_name'));
}
Would you approve this solution? What problems can cause it to me? Is it the proper way to do it?
Thanks all!

How to swap Laravel implementation with Behat and Mockery

I have a Behat FeatureContext for which I want to swap a Laravel implementation of a given class with a mocked one.
so I have this method, with a #beforeSuite annotation
/**
* #static
* #beforeSuite
*/
public static function mockData()
{
$unitTesting = true;
$testEnvironment = 'acceptance';
$app = require_once __DIR__.'/../../../bootstrap/start.php';
$app->boot();
$fakeDataRetriever = m::mock('My\Data\Api\Retriever');
$fakeData = [
'fake_name' => 'fake_value'
];
$fakeDataRetriever->shouldReceive('getData')->andReturn($fakeData);
$app->instance('My\Data\Api\Retriever', $fakeDataRetriever);
}
So I see the Laravel app and the fake data being swapped, but when I run Behat, it is being ignored, meaning Laravel is using the actual implementation instead of the fake one.
I'm using Laravel 4.2
Does someone know a way to swap Laravel implementations when running Behat?
The reason I need this is because the data is coming from remote API and I want the test to run without hitting the API.
I'm not too familiar with Behat besides what I just read in a quick tutorial to see if I can help found here... http://code.tutsplus.com/tutorials/laravel-bdd-and-you-lets-get-started--cms-22155
It looks like you are creating a new instance of Laravel, setting an instance implementation inside of it, then you are not doing anything with the Laravel instance. What's likely happening next is the testing environment is then going ahead and using its own instance of Laravel to run the tests on.
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use PHPUnit_Framework_Assert as PHPUnit;
use Symfony\Component\DomCrawler\Crawler;
use Illuminate\Foundation\Testing\ApplicationTrait;
/**
* Behat context class.
*/
class LaravelFeatureContext implements SnippetAcceptingContext
{
/**
* Responsible for providing a Laravel app instance.
*/
use ApplicationTrait;
/**
* Initializes context.
*
* Every scenario gets its own context object.
* You can also pass arbitrary arguments to the context constructor through behat.yml.
*/
public function __construct()
{
}
/**
* #BeforeScenario
*/
public function setUp()
{
if ( ! $this->app)
{
$this->refreshApplication();
}
}
/**
* Creates the application.
*
* #return \Symfony\Component\HttpKernel\HttpKernelInterface
*/
public function createApplication()
{
$unitTesting = true;
$testEnvironment = 'testing';
return require __DIR__.'/../../bootstrap/start.php';
}
/**
* #static
* #beforeSuite
*/
public function mockData()
{
$fakeDataRetriever = m::mock('My\Data\Api\Retriever');
$fakeData = [
'fake_name' => 'fake_value'
];
$fakeDataRetriever->shouldReceive('getData')->andReturn($fakeData);
$this->app->instance('My\Data\Api\Retriever', $fakeDataRetriever);
}
}

How to use com_users model class in my custom component in joomla

I have created a custom component in Joomla 2.5. In this component I want to fetch all the user 's are available in com_users.For this I want you to know, How can i use com_users model class in to my component. Any one have suggestion's to how to do it.
Depending on where you want use the model you can simply ask Joomla! to load it for you.
In a JController class or sub-class you can call getModel passing in the model name and the components prefix...
e.g.
JModel::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_users/models/');
$model = $this->getModel($name = 'User', $prefix = 'UsersModel');
It may be necessary to add the path of the external model you want to load using JModel::addIncludePath() as show above.
Or if you're sure of the model name and the class prefix you could use JModel's getInstance() to create the desired model object... e.g.
$model = JModel::getInstance('User', 'UsersModel');
Alternatively in a view you could:
$myModel = $this->getModel('myOtherModel');
$this->setModel($myModel);
N.B. In the first line we're passing our desired models name, normally you call getModel without any params to load the default model for your components view controller. In the second line, as we're only passing the model to setModel() it won't make it the default model the view uses.
When we want to use our model objects later on we can specify which we want to use like this:
$item = $this->get('Item');
$otherItem = $this->get('Item', 'myOtherModel' );
The first line uses the view's default model (because we have specified one in the optional parameter). The second line uses the getItem() from myOtherModel.
That's all works because JView (in libraries/joomla/application/view.php) has these methods:
/**
* Method to get the model object
*
* #param string $name The name of the model (optional)
*
* #return mixed JModel object
*
* #since 11.1
*/
public function getModel($name = null)
{
if ($name === null)
{
$name = $this->_defaultModel;
}
return $this->_models[strtolower($name)];
}
/**
* Method to add a model to the view. We support a multiple model single
* view system by which models are referenced by classname. A caveat to the
* classname referencing is that any classname prepended by JModel will be
* referenced by the name without JModel, eg. JModelCategory is just
* Category.
*
* #param JModel &$model The model to add to the view.
* #param boolean $default Is this the default model?
*
* #return object The added model.
*
* #since 11.1
*/
public function setModel(&$model, $default = false)
{
$name = strtolower($model->getName());
$this->_models[$name] = &$model;
if ($default)
{
$this->_defaultModel = $name;
}
return $model;
}
Try something like this
if(!class_exists('UsersModelUser')) require(JPATH_ROOT.DS.'administrator'.DS.'components'.DS.'com_users'.DS.'models'.DS.'user.php');
You can add correct path the model from admin side or front end.
The VM2.x component is using like this way.
Or you need only some details about user you can use.
$user = JFactory::getUser();
Hope this may help you..

Resources