I'm developing a PHP CMS and I have question about flexible and «extendable» architecture.
I want to allow users to «extend» every class in system.
For example, if I have class User:
<?php
namespace MyCMSName\Extensions\Users;
class User
{
public function getUserAvatar()
{
// return file path, for example
}
}
Now I want to do something with users avatars. Return different image for each user, for example.
<?php
namespace MyCMSName\Extensions\FakeUserAvatars;
class User extends MyCMSName\Extensions\Users\User
{
public function getUserAvatar()
{
// return another path
}
}
But somewhere in system I have User object constructing. I need to create FakeUserAvatars\User, not original Users\User object.
...
$user = new (? How can I determine which class to construct?)
Should I create something about map «original class → extended class» and then generate it when installing new extensions? Then I can get class which overload original and create new instance. But what about autoloading? It seems that I should have custom function for load every class in system if I want to make it «extendable».
What do you think?
Sorry if I wrote something wrong, English isn't my native language...
Related
These are my classes
class DB{
// returns the instance as it should - all system works well
public function create(){
// creates a row in the db....
}
}
class User{
// does all it does....
}
I have an autoloader that loads the classes and the system works well
$user = new User();
$user->create(....)
I want to use the method create from the DB while instantiating User class, how can I do so?
the thing that I want to prevent is not having to need to create a function "update" in every class which needs to be updated
For example:
Class user - update "users table"
class kids - update "kids table"
instead
// from DB class
update($table_name, $field)
The autoloader is responsible for finding the and including the files that contain the class definitions so you don't have to explicitly call include 'db.php'; etc. , but it isn't going to be able to do what you're trying to do here.
If the create() method is defined in the DB class, there's no reason for it to be accessible through an instance of a different class like User, and unless you specifically do something to allow it, the autoloader won't help with that.
The only way for you to access the DB::create() method from an instance of User is for User to extend DB.
class User extends DB { ...
This means that all User objects will also be instances of DB and have access to all of the public and protected methods of DB, not just create().
It looks like this might be what you want. If that's the case, it would be helpful for you to read up on Object Inheritance.
I am currently developing a CMS package, similar to the django's administration site, for laravel.
What I'm trying to achieve is the feature on django where you can simply create a class to build a model block on the admin panel, specifically for the relationships on any model.
With that said, to show any model on the admin panel it's necessary to create a class, just like django's, and provide a little configuration.
This is where the things become a little messy.
Let's say that there are two classes:
class PermissionAdmin extends Admin{}
class RoleAdmin extends Admin {}
Routes:
Route::get('/{slug}', 'DashboardController#method'); //Show model list
Route::post('/{slug}', 'DashboardController#method'); //Create a new instance of a model
Route::get('/{slug}/{id}', 'DashboardController#method'); //Show a model
Route::post('/{slug}/{id}', 'DashboardController#realMethod'); //Updates a model
Now we're going to focus on DashboardController#realMethod
Controller:
class DashboardController extends Controller {
function realMethod(Request $request, $slug, $id){
$className = ucfirst($slug)."Admin";
$ModelAdmin = class_lookup($className);
}
}
class_lookup definition:
function class_lookup($class_name){
if(class_exists($class_name))
return new $class();
else if(class_exists("Package\\Namespace\\".$class_name)){
$class = "Package\\Namespace\\".$class_name;
return new $class();
}
else{
throw new Exception("Class not found");
}
}
So, if we want to see the list of permissions available, we need to go to localhost/permission, the same for the roles: localhost/roles. The slug is what I use to search on the default laravel app directory or in the vendor directory. When the dependency is "solved" the process, (create, update) is able to continue.
I want to enhance that dependency process and maybe with service container bindings it's possible but I'm still trying to figure out.
It will be great if I could have something like this:
class DashboardController extends Controller {
function realMethod(ModelAdmin $model){
$modelAdmin = $model;
......
}
}
in order to avoid repeating the class_lookoup($className) on every method of the controller.
You guys know any way to make it work like that?
I'am a Brazilian developer, so... sorry for my limited English right away.
Well, in fact my problem is more a convention problem because until now I hadn't use services with Laravel (my apps were that simple so far).
I read about it before ask this question, but nothing helped with this specific situation. I'll try to describe in a objective way.
before that, just a comment: I know about the mistake using just controllers in these example. The ask is really about that mistake.
Well, the actual structure is:
abstract class CRUDController extends Controller {
protected function __construct($data, $validatorData) {
// store the data in a attribute
// create with Validator facade the validation and store too
}
abstract protected function createRecord();
protected function create() {
try {
// do the validation and return an Response instance with error messages
// if the data is ok, store in the database with models
// (here's where the magic takes place) in that store!
// to do that, calls the method createRecord (which is abstract)
$this->createRecord();
// return a success message in an Response instance
}
catch(\Exception $e) {
// return an Response instance with error messages
}
}
}
class UserController extends CRUDController {
public function __construct($data) {
parent::__construct($data, [
'rules' => [
// specific user code here
],
'messages' => [
// specific user code here
],
'customAttributes' => [
// specific user code here
]
]);
}
protected function createRecord() {
$user = new UserModel();
// store values here...
$user->save();
return $user;
}
}
// here's the route to consider in that example
Route::post('/user', 'WebsiteController#register');
class WebsiteController extends Controller {
private $request;
public function __construct(Request $request) {
$this->request = $request;
}
public function register() {
$user = new UserController();
$user->create($this->request);
// here's the problem: controller working with another controller
}
}
class UserAPIController extends Controller {
// use here the UserController too
}
and many other classes that extends CRUDController in the same way...
What I want
I want to create a controller (called here as CRUDController) to reuse methods like the pattern says (create, read, update and delete).
To be really objective here I'll use the create method as an example.
With the code above it seems clear the purpose? I think so... all my controllers have that code of validation equal and reusable. That's the thing.
Besides that, I want to my route of website call another controller (UserController) to store new users... but in the same way, I'll create an API that uses the same controller in the same way (with validations etc). That's the purpose of Responses in the CRUDController (I'll read them in the WebSiteController to resolve what to do, like show a view and in the other hand with the API I'll basically return the Response.
My real problem
Convention and pattern. The MVC pattern is broken here. Controller calling another controller is wrong and I know that.
I want to know what thing I should use! Services? Is that right? I see a lot (really) of examples of services but nothing like that, working with models and reusing code, etc. I never use Services but I know how to use, but I don't know if it's right to these cases.
I really hope that someone can help here and sorry once again for the mistakes with the English. Thanks a lot.
You're calling the CRUD controller a controller but it does not behave as an MVC controller. At best it's just a helper class. You could always do this:
abstract class CRUDManager {
//As you had the CRUDController
}
class UserManager extends CRUDManager {
//As you had the UserController
}
In your AppServiceProvider:
public function boot() {
$app->bind(UserManager::class, function ($app) {
return new UserManager(request()->all()); //I guess that's what you need.
});
}
Whenever you need to use it you can do:
public function register(UserManager $user) {
$user->create();
}
Now one thing to point out. It's not a good idea to initialise the request in the constructor. You should use dependency injection in controller methods. I don't even know if the request is available when the controller is being constructed (I know the session is not). The reason why I say this is that the middleware runs after the controller is constructed and therefore the request may be modified when the controller method is called.
Another note: If you did the original solution because you needed to use certain controller methods, then you can just use the corresponding traits (because the controller itself does not really have many method). I'm guessing a trait like ValidatesRequests would be one you'd need to use.
I'll answer my own question. I use a pattern called Repository Pattern to resolve the problem (or I try to use, because it's the first time using this pattern: maybe I don't use in the right way in every steps).
Files structure
Controllers
UserController.php
Models
UserModel.php
Providers
UserRepositoryServiceProvider.php
Repositories
RepositoryInterface.php
Repository.php
User
UserRepositoryInterface.php
UserRepository.php
Traits
InternalResponse.php
With that structure I did what I wanted in my question without working just with controllers.
I create a trait called InternalResponse. That trait contains a few methods that receive a transaction, validate if it's the case and then return a Response (called "internal" in my logic because the controller will read and maybe change the Response before return it in the end).
The Repository class, which is abstract (because another class must extend it to make sense to use. In this case the class UserRepository will extend...), uses the Trait mentioned.
Well, with it in mind, it's possible to know that the UserController uses the UserRepositoryInterface, that provides an object UserRepository: because the UserRepositoryServiceProvider register this with that interface.
I think there's no need to write code here to explain, because the problem is about an pattern, and these words explain well the problem (in the question) and the resolution with this answer here.
I'll write here a conclusion, I mean, the files structure with comments to explain a little bit more, to end the answer.
Conclusion: Files structure with comments
Controllers
UserController.php
// the controller uses dependency injection and call methods of
// UserRepository, read and changes the Response receveid to finally
// create the final Response, like returning a view or the response
// itself (in the case it's an API controller)
Models
UserModel.php
// an normal model
Providers
UserRepositoryServiceProvider.php
// register the UserRepositoryInterface to
// return a UserRepository object
Repositories
RepositoryInterface.php
// the main interface for the Repository
Repository.php
// the main repository. It's an abstract class.
// All the others repositories must extend that class, because
// there's no reason to use a class Repository without an Model
// to access the database... That class share methods like create,
// read, update and delete, and the methods validate and transaction
// too because uses the trait InternalResponse.
User
UserRepositoryInterface.php
// the interface for UserRepository class
UserRepository.php
// that class extend Repository and uses the UserModel
Traits
InternalResponse.php
// trait with methods like validate and transaction. the method
// validate, read and validate the data receveid for the methods
// create and update. and all the CRUD methods uses the method
// transaction to perform the data to the database and return a
// response of that action.
That's what I do and like I said before, I don't know if it's a hundred percent correct in reference to Repository Pattern.
I hope this can help someone else too.
Thanks for all.
So Laravel saves it's own session files when someone accesses the website in the /storage/framework/sessions folder. Each of these session file's names are a randomly generated alpha numeric unique name. But, I'd like to somehow rename the files and give my own custom name for it. I've got two options for that.
Change the file name manually once the session file is created (by a create, copy, replace)
Find the function which randomly generates the alphanumeric name and change it with my own way of setting a unique name to each file (this method might come with less complications)
My main end goal is to rename each user's session file to their own userid that's stored in my db. So the names are still unique, the only difference is that I can search through the files easier than if they had random alphanumeric names.
So if anyone knows how I could do any of the above methods or if you can think of a better way to achieve the same, it'd be great. Any help is greatly appreciated!
EDIT: Decided to update here with what I had decided to do finally. I decided not to use the built in session files generated by Laravel and realized it's much easier to make my own file and just have each client access it instead. Thanks to all!
Laravel has several Manager classes that manage the creation of
driver-based components. These include the cache, session,
authentication, and queue components. The manager class is responsible
for creating a particular driver implementation based on the
application's configuration. For example, the SessionManager class can
create File, Database, Cookie and various other implementations of
session drivers.
Each of these managers includes an extend method which may be used to
easily inject new driver resolution functionality into the manager.
To extending Laravel with a custom session driver, we will use the
extend method to register our custom code:
You should place your session extension code in the boot method of your AppServiceProvider.
Implement SessionHandlerInterface
app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
use Session;
use Illuminate\Support\ServiceProvider;
use App\Handlers\MyFileHandler;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Session::extend('file', function($app)
{
return new MyFileHandler();
});
}
}
Note that our custom session driver should implement the SessionHandlerInterface. This interface contains just a few simple methods we need to implement.
app/Handlers/MyFileHandler.php
<?php
namespace App\Handlers;
use SessionHandlerInterface;
class MyFileHandler implements SessionHandlerInterface {
public function open($savePath, $sessionName) {}
public function close() {}
public function read($sessionId) {}
public function write($sessionId, $data) {}
public function destroy($sessionId) {}
public function gc($lifetime) {}
}
Or you can extend MyFileHandler from FileSessionHandler and override relevant methods.
Extend FileSessionHandler
app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
use Session;
use Illuminate\Support\ServiceProvider;
use Illuminate\Session\FileSessionHandler;
use App\Handlers\MyFileHandler;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Session::extend('file', function($app)
{
$path = $app['config']['session.files'];
return new MyFileHandler($app['files'], $path);
});
}
}
app/Handlers/MyFileHandler.php
<?php
namespace App\Handlers;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Session\FileSessionHandler;
class MyFileHandler extends FileSessionHandler
{
public function __construct(Filesystem $files, $path)
{
parent::__construct($files, $path);
}
}
You can find more in Session section of Extending the framework document.
https://laravel.com/docs/5.0/extending#session
If your final goal is searching on session file names; you don't need to change them.
You can save session file names in a database table ( or another file your choice). You can use this link to get file names.
One column -> store session file names
other columns -> store another informations that you want
In this way you can search and find faster files with using SQL.
use middleware for the request
\Illuminate\Session\Middleware\StartSession::class
Route::group(['middleware' => [\Illuminate\Session\Middleware\StartSession::class]], function () {
});
I am working on building a lightweight MVC, mainly for the learning process but I would like it to be good enough to use eventually.
Below is a basic example/demo of how a basic controller might would look, let's assume the URI has been processed and routed to this controller and these 2 methods.
1) I need to get data from database/cache/etc... inside my Model classes, I just need help on how I should load my models into my example controller below, you can see that I have added this below $profileData = $this->model->getProfile($userId) that is just made up and does not exist's, how could I get something like that to work though? Or should I load the model into the class a different way?
2) A lot of pages will require a user to be logged into the site. SHould I process that part below in the controller to check if a user is logged in, example, before building the profile page, check if user is logged in, if not then build a login page instead and add these checks inside of each controller method/page?
/**
* Example Controller
*/
class User_Controller extends Core_Controller {
// domain.com/user/id-53463463
function profile($userId)
{
//GET data from a Model
$profileData = $this->model->getProfile($userId);
$this->view->load('userProfile', $profileData);
}
// domain.com/user/friends/
function friends()
{
//GET data from a Model
$friendsData = $this->model->getFriendlist();
$this->view->load('userFriends', $friendsData);
}
}
core
abstract class Core_Controller {
protected $view;
protected $model;
function __construct(DependencyContainer $dependencyContainer){
$this->view = new Core_View();
//$this->view = $dependencyContainer->get(view);
}
}
There are probably tons of ways to accomplish what you are trying.
The "easiest" is probably to just override the constructor and instantiate the model directly.
in User_Controller:
public function __construct(DependencyContainer $dc) {
parent::__construct($dc);
$this->model = new User_Model();
}
I'm guessing that you are looking for something a little more automated though. If you want the Model to have the same name as the controller minus "_Controller", just use get_class($this) in the constructor and use PHP's string functions to parse out what you want. Once you have that in a variable, you can use that variable to instantiate the model:
in Core_Controller:
public function __construct(DependencyContainer $dc) {
$this->view = new Core_View();
// $model_class should be 'User_Model' now
$model_class = str_replace('_Controller', '_Model', get_class($this));
// now instantiate the model
$this->model = new $model_class();
}
I haven't actually worked with any framework that can only have one model associated with each controller (except may CakePHP? I can't remember). With Symfony, the models and controllers are completely decoupled so you can use any model with any controller. You just instantiate the model as need. Symfony use the Doctrine ORM so for example, in a controller action, if you needed a model you would do something like this:
$model = Doctrine::getTable('User');
It might be worthwhile to consider a design more like that in order to promote a decoupled design and I promise that you will want more than one model in some controller at some point.
2.) As far as authentication. Something that seems to be fairly common is to have some sort of setting (whether in a config file or a member variable) that says whether or not the current action needs the user to be authenticated. This is processed each time the action runs (Yii calls these kinds of things filters). If the user needs to be logged in, it stores the page that they are trying to access, and then redirects them to a log in page (you should only ever have to create one). Once they properly authenticate, it will redirect them back to where they were originally heading.