Best way to add custom validation method in a CodeIgniter 4 module - codeigniter-4

Here is the problem.
I've got a module called Contact. It lives in a directory called modules/Contact. I registered a namespace Contact for the module in app/Config/Autoload.php. Everything seems to load fine. Now I want to use a custom validation method to validate input coming from a form field. My question is about the best way to do it. My approach is as follows: According to CI4 documentation, Validation class loads App/Config/Validation.php when used for validation. In the file there is public property called $ruleSets, which points to an array, it contains names of classes with validation rules. I want to add my own class with validation methods to this array. According to CI4 documentation on validation, configuration classes, and their public properties in App/Config/ can be updated using registrars. So, I crated one such registrar, a class, in my Contact module. It lives in Contact\Config\Registrar.php . The class has public static method Validation, which returns an array with an updated $ruleSet. The new value is an array and contains all the standard validation class names + my validation class name. The trouble is public static method Validation I added in my registrar seems not to change the value of $ruleSet defined in App/Config/. Contact/Config/Registrar.php is loaded, but Validation method it contains is not called. So, what's the best method to add custom validation rules without having to updated files which live beyond my module? There is very little on registrars in CI4 documentation. Have I misunderstood how they work and how they are supposed to be used?

I had the same problem and my solution was to extend the validation class (better solutions are always welcome).
For example I wanted to write my own Auth module and use my own set of rules. My custom validation class lives under acme/Auth/Config/AuthValidation.php
<?php
namespace Acme\Auth\Config;
use Config\Validation;
class AuthValidation extends Validation
{
public $login = [
'username' => 'required',
'password' => 'required',
];
}
This way all validation rules in your main app are inherited.
And you would use this code in your controller to validate:
<?php
namespace Acme\Auth\Controllers;
use CodeIgniter\Controller;
use Acme\Auth\Config\AuthValidation as AuthValidation;
class Auth extends Controller
{
public function login()
{
//Instantiate the new AuthValidation class
$authValidationConfig = new AuthValidation();
//Since the validation class is a config for the validation service we set it for the validation service
$authValidation = \Config\Services::validation($authValidationConfig);
//Set the login rule from our AuthValidation class
$authValidation->setRuleGroup('login');
//Validate the request against our login rule
if($authValidation->withRequest($this->request)->run())
{
return 'Success';
}
else
{
return 'Failure';
}
}
}
It may be not the best solution because I'm pretty new to CodeIgniter but this is how I found my way around it.
Sidenote: The main problem seems to be that the class passed to the validation service constructor has to be an instance of "Config\Validation" so I had to extend it. But there might be other ways around it.
Feel free to correct me :)

Related

Dynamic determination for database

I have some entity mapping to a collection in a mongoDB. However, I have a separate database I want the same code to run against (eg. Constants.db_B). How can I make this parameter dynamic so I can run this without changing the constant and recompiling? If I could pass this as a parameter that would be fine too.
#MongoEntity(collection = "sample", database = Constants.db_A)
public class SomeEntity extends PanacheMongoEntity {
}
I tried using ConfigProperty on the Constants class, but it only injects it on a bean instance and not the class itself.
Is this possible?
It's not possible right now. This has been issued in the quarkus project.
https://github.com/quarkusio/quarkus/issues/14789
It's not the same casuistry than yours but there's a solution made from the Author of that issue, may be helpful for you
https://gitlab.com/ch3rub1/quarkus-mongo-mutitenant

FakeItEasy in C# on a servicereference

I have a servicereference with a method I need to use in a test.
The servicereference class is defined as:
public class MyServiceReference : Clientbase<IMyServiceReference>, IMyServiceReference
{
public MyServiceReference()
{
}
..... methods is then defined
}
From my testmethod I have tried both
private MyServiceReference myServiceReferenceFake = A.Fake<MyServiceReference>();
// And
private MyServiceReference myServiceReference = new MyServiceReference();
For both of these is crashes in the constructor with the message:
System.InvalidOperationException: Could not find default endpoint element that references contract.
All I need is to have a callto definition from a method in that class.
How can this be solved?
I've no experience with Clientbase, which I assume to be a System.ServiceModel.ClientBase<TChannel>,but I can make some general comments.
Since you tried first to fake a MyServiceReference, I'll assume that you're not testing that class, and you want to use it as a collaborator for the system under test. In that case, your best bet is to try faking IMyServiceReference. interfaces are very easy to fake, since they don't bring along any behaviour or baggage like faking a class does.
If you feel you really need to fake a MyServiceReference, then we have to contend with the fact that FakeItEasy will eventually call MyServiceReference(), which will call ClientBase<IMyServiceReference>(), whose documentation says
Initializes a new instance of the ClientBase<TChannel> class using the default target endpoint from the application configuration file.
Based on the error you reported, I assume that the application configuration file is not found or does not include the configuration required to create a MyServiceReference. The fact that you get the same error when you just try to instantiate a MyServiceReference directly strengthens my belief.
So I think your paths forward are either to try faking IMyServiceReference or to provide the configuration that ClientBase<IMyServiceReference> needs.

How to use default constructor in codeIgniter4?

I want to use controller to load directly some models by using default constructor, but CodeIgniter4 was removed __construct method from CodeIgniter4 framework and I got error message Cannot call constructor. please see code below:
public function __construct(){
parent::__construct();
//Do magic task here
}
Instead of CI3 is working by using above code, Can anyone suggest me in CI4?
Thank You for your tips or comments!
Best Regards!
CI4 default controller namespaced CodeIgniter\Controller doesn't have a class constructor. So if you're extending directly from it, parent::__construct() can't be called.
If you really need a constructor for every one of your controller you should modify App\Controllers\BaseController and making your others controllers extending it with the code you provided.
Also if your goal is to execute some code before or after your controller is called you should check out Filters in CI4. They are perfectly designed for this need : https://codeigniter4.github.io/userguide/incoming/filters.html
You can use constructor in CI4 to load models
Add the code in your Controller class and it should work.
protected $userModel;
public function __construct()
{
$this->userModel = new UserModel();//Create a instance of the model
helper('form', 'url');
}

Buiding Orchard Module - Telling Autofac to use different implementations

As I exposed here, I would like to be able to query freely on the index without Orchard building the query for me.
I built a module, and inserted a copy of the SearchController, adding a new route...
To override default Orchard behavior concerning the query, I had to create new implementations of : ISearchService, IIndexManager, ISearchBuilder, IIndexProvider.
There are minor modifications from their default implementations, but they are needed.
That works as expected, but it currently override the default search too.
This is because I used the same interfaces and autofac takes my implementations.
I would like to be able to leave the default implementation untouched (at url /Search), and add my implementation at url (/LuceneSearch for example)
I suppose I must tell Autofac to use my implementations only for my controller by creating a class that inherits the autofac Module class.
Here is my problem : I don't know how to tell Autofac to use by default the Orchard Implementation, and just for my controller, use my implementation....
Another possibility is to create new interfaces, but it seems to me not really beautiful...
Can someone help me ? :)
Metadata feature will help you here. Also you have to register your implementations with PreserveExistingDefaults() modifier to preserve orchard's implementations by default.
Update:
Orchard registers all dependencies from Autofac's IModule implementation and from Orchard's IDependency one. All magic happen in Orchard's ShellContainerFactory class. Since ISearchService inherits from IDependency your implementation is registered by Orchard which overwrites default one.
You have two ways here:
Introduce your own empty interface IMySearchService which inherits from ISearchService. Implement and use it in your code where you need your implementation. Orchard will handle all registrations for your.
Orchard registers all IDependency implementations with "Feature" Metadata in ShellContainerFactory.RegisterType method. You can read this metadata in your code and choose your implementation (see link to wiki above). Feature class contains all necessary information about your module.
Hope this will help you.
A simpler method without dabbling with the intricacies of Autofac would be to use an IEnumerable<IInterface> variable in your controller/driver at url "/LuceneSearch" to hold all implementations of IInterface and choose which to consume.
For example, in order to consume your implementation of IIndexManager, you would put the following in your controller or driver
public class MyCustomPartDriver : ContentPartDriver<MyCustomPart> {
private readonly IEnumerable<IIndexManager> _indexManagers;
public MyCustomPartDriver(IEnumerable<IIndexManager> indexManagers) {
_indexManagers = indexManager;
}
protected override DriverResult Display(MyCustomPart part, string displayType, dynamic shapeHelper) {
//Use your implementation of IIndexManager
var indexManager = _indexManagers.First(im => im is MyCustomIndexManager);
//Get the ISearchBuilder from your implementation of IIndexManager
var searchBuilder = indexManager.HasIndexProvider() ? indexManager.GetSearchIndexProvider().CreateSearchBuilder("Search") : new NullSearchBuilder();
//perform search on the indexing engine
var contentItemIds = searchBuilder.
.WithField("type", "MyCustomType").Mandatory().ExactMatch()
.Parse("mycustompart-keyword", part.Keyword).Mandatory()
.Search()
.Select(h => h.ContentItemId)
.ToList();
//do stuff with the results returned and return a DriverResult using the ContentShape method. Well, you know the drill.
}
}
If you don't want to have autofac resolve your own implementation by default, then don't implement the public interface.

Orchard Custom User Part is not stored in Database

I can't seem to store additional data in a separate contentpart attached to User. I have done the following:
Created a module
In the module I created a Model for ProfilePart and ProfilePartRecord
In the migration I created a table for ProfilePartRecord (from type ContentPartRecord)
In the migration I altered the typedefinition for User, by setting WithPart ProfilePart
I created a driver class, that has 2 edit methods, one for get and one for post (code snippets are below
I also created a handler that adds a storage filter for profilePartRepository of type ProfilePartRecord
Module Structure
Drivers/ProfilePartDriver.cs
Handlers/ProfileHandler.cs
Models/ProfilePart.cs
Models/ProfilePartRecord.cs
Views/EditorTemplates/Parts/profile.cshtml
Migrations.cs
Placement.info
Since I think the issue is in the Driver. This is my code:
Is it going wrong because the part is attached to User? Or am I missing something else.
public class ProfilePartDriver:ContentPartDriver
{
protected override string Prefix
{
get { return "Profile"; }
}
//GET
protected override DriverResult Editor(ProfilePart part, dynamic shapeHelper)
{
return ContentShape("Parts_Profile_Edit", () =>
shapeHelper.EditorTemplate(TemplateName: "Parts/Profile", Model: part, Prefix: Prefix));
}
//POST
protected override DriverResult Editor(ProfilePart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
I have used Skywalker's blog. There is one chapter about registering customers by using the User and adding your own content parts to it. Worked nice for me.
First of all - is your ProfilePart editor shown at all when you go to Dashboard and edit a given user? I noticed you're using Parts_Profile_Edit as a shape key, but actually use EditorTemplates/Parts/Profile.cshtml as a template. It's perfectly correct, but note that Placement.info file uses shape keys, so you have to use Parts_Profile_Edit as a shape name in there. Otherwise it won't get displayed.
Second - have you tried debugging to see if the second driver Editor method (the one for handling POST) is being called at all?
Like Bertrand suggested, I'd look into one of the existing modules that work (afaik there is one for user profile in the Gallery) and see the difference. It might be something small, eg. a typo.

Resources