Dynamic form building / metaprogramming / PHP - php

I'm looking for design ideas about creating dynamic forms.
What I currently have is a bunch of models with a few properties / variables for example:
class Group extends IDKModel {
/**
* #Form
*/
public $title;
/**
* #Form(validation="{my validation rules here}")
*/
public $permissions;
}
In that example the Form PHPAnnotation defines it as a form element and the validation array will have built in validation rules in it. Now the problem I'm having is I have no idea how to implement conditionals.
For example, how to show $permissions only if a user is an admin. How to show $title if time of day is past 12:00 GMT. Basically any kind of conditional.
Taken from #php at irc.quakenet.org:
[10:50] <ramirez> i would not try to stick all of the meta-info under one attribute
[10:50] <ramirez> it'd be much cleaner to do something like
[10:50] <ramirez> #FormElement
[10:51] <ramirez> #Validator(params)
[10:51] <ramirez> etc
[10:52] <ramirez> anyways, I would probably do something like.. #Filter(name="Group",value="admin,editor")
[10:53] <ramirez> then for each filter you want to implement, you'll create a class like "Model_Filter_Group", which would be used for eg. the above filter
[10:53] <ramirez> that class in this case would simply explode the groups by comma and see if user is in any of those groups
[10:54] <ramirez> you can use that for any kind of filtering, eg: #Filter(name="PastTime", value="12:00")
Anyone have a simpler idea?

I suggest you take a look to Zend_Form component in Zend Framework:
Zend Framework Reference:Zend_Form
Zend_Form is very powerful and highly customizable but can be a bit too complex to see all of its capabilities provided by good OOP design.
Even if ZF of no use for you, you can find good ideas there.
btw: manual covers only basics as well as tutorials found on the web

Related

What is a macro in laravel Macroable

please can anyone help me understand what a macro is in Laravel Macroable trait, reading this documentation https://laravel.com/api/5.4/Illuminate/Support/Traits/Macroable.html only tells me how to use but why do I use it, what is it meant for.
It is for adding functionality to a class dynamically at run time.
use Illuminate\Support\Collection;
Collection::macro('someMethod', function ($arg1 = 1, $arg2 = 1) {
return $this->count() + $arg1 + $arg2;
});
$coll = new Collection([1, 2, 3]);
echo $coll->someMethod(1, 2);
// 6 = 3 + (1 + 2)
echo $coll->someMethod();
// 5 = 3 + (1 + 1)
We have 'macroed' some functionality to the Collection class under the name someMethod. We can now call this method on the Collection class and use its functionality.
We just added a method to the class that didn't exist before without having to touch any source files.
For more detail of what is going on, please check out my article on Macros in Laravel:
asklagbox - blog - Laravel Macros
It allows you to add new functions. One call to ::macro adds one new function. This can be done on those of the internal framework classes which are Macroable.
This action of adding the function to the class is done at run time. Note there was/is an already existing perfectly good name for this action, which isn't the word "macro", which I'll explain at the end of this post.
Q. Why would you do this?
A. If you find yourself juggling with these internal classes, like
request & response, adding a function to them might make your code more
readable.
But as always there is a complexity cost in any
abstraction, so only do it if you feel pain.
This article contains a list of the classes you can add functions to using the static call "::macro"
Try not to swallow the word macro though, if you read that article - if you're like me it will give you big indigestion.
So, let's now add one extra function to an internal framework class. Here is the example I have just implemented:
RedirectResponse::macro('withoutQuery', function() {
return redirect()->to(explode('?', url()->previous())[0]);
});
This enables me in a controller to do this:
redirect()->back()->withoutQuery();
(You can just do back() but I added redirect() to make it clear).
This example is to redirect back and where the previous route was something like:
http://myapp.com/home?something=something-else
this function removes the part after '?', to redirect to simply:
http://myapp.com/home
I did not have to code it this way. Indeed another other way to achieve this is for me to put the following function in the base class which all controllers inherit from (App\Http\Controllers\Controller).
public function redirectBackWithoutQuery()
{
return redirect()->to(explode('?',url()->previous())[0]);
}
That means I can in any controller do this:
return $this->redirectBackWithoutQuery();
So in this case the "macro" lets you pretend that your new function is part of an internal framework class, in this case the Illuminate/RedirectResponse class.
Personally I like you found it hard to grasp "laravel macros". I thought that because of the name they were something mysterious.
The first point is you may not need them often.
The second point is the choice of the name ::macro to mean "add a function to a class"
What is a real macro?
A true macro is a concept unique to Lisp. A macro is like a function but it builds and returns actual code which is then executed. It is possible to write a function in other languages which returns a string which you then execute as if it was code, and that would be pretty much the same thing. However if you think about it you have all of the syntax to deal with when you do that. Lisp code is actually structured in lists. A comparison might be imagine if javascript was all written as actual json. Then you could write javascript, which was json, which returned json, which the macro would then just execute. But lisp is a lot simpler than json in terms of its syntax so it is a lot easier than what you just imagined. So, a true lisp macro is one of the most beautiful and amazing things you can encounter.
So why are these add-a-function things in laravel called macros?
That's unknown to me I'm afraid, you'd have to ask the author, but I asked myself what they really do and is there already a name for that.
Monkey Patches
TL;DR laravel's ::macro could more accurately be described as monkey patch
So if using laravel ::macro calls, I personally decided to create a MonkeyPatchServiceProvider and put them all there, to reduce unnecessary confusion for myself.
I realise the name might sound a bit derogatory, but that's not intended at all.
It's simply because there's already a name for this, and we have so much terminology to deal with why not use an existing name.

Integrating Mobile Money to Symfony

I have a Symfony application which I will like to integrate mobile money into. The problem is I cannot add PHP code to twig files and I am a complete newbie to this kind of challenge. The code reads:
<?php
require_once '/path/to/monetbil-php/monetbil.php';
// Setup Monetbil arguments
Monetbil::setAmount(500);
Monetbil::setCurrency('XAF');
Monetbil::setLocale('en'); // Display language fr or en
Monetbil::setPhone('');
Monetbil::setCountry('');
Monetbil::setItem_ref('2536');
Monetbil::setPayment_ref('d4be3535f9cb5a7aff1f84fa94e6f040');
Monetbil::setUser(12);
Monetbil::setFirst_name('KAMDEM');
Monetbil::setLast_name('Jean');
Monetbil::setEmail('jean.kamdem#email.com');
// Start a payment
// You will be redirected to the payment page
Monetbil::startPayment();
I am looking at adding this to App/Resources/Views/members/dashboard.html.twig
Twig is only ment to render your output. Put your (php) logic in your controller and/or create your own service. From your controller you will also render your Twig template with the variables you need but only to render the output that you want to show to the users.
If you're using a framework like Symfony, you shouldn't use require_once (except some exceptions). Read about autoloading and dependency injections (Symfony.com has excellent articles).
For some reason, the monetbil-php library doesn't use composer. I don't know why, but I can imagine three reasons: they don't know what it is (hello developers, it's 2017!), they hate other developers or the library hasn't been updated for years. Despite the recent commits, it looks like a very outdated library (why still supporting PHP 5.2? That's from the dark ages!). Sorry for this slightly offtopic rant, back to the question.
For now, copy the files to your project, give the file a namespace and use it in your project. I've opened an issue here because I think the developers should add a composer file if they want their users to use their library in a framework like Symfony.
The PHP code should be in your controller* since you can't use PHP in Twig and even if you could, you shouldn't. Monetbil defines business logic, so it shouldn't be in a template.
To use it in your controller:
/**
* Pay the bill
*
* #Route("/pay/{id}", name="payment")
* #Method("POST")
* #param Request $request
* #param Order $product
*
* #return JsonResponse
* #throws \Exception
*/
public function payAction(Request $request, Order $product)
{
Monetbil::setAmount(500);
//..
Monetbil::startPayment();
}
According to the comment, startPayment() will redirect, so there's nothing to return to the Twig template.
(*) Business logic in your template is considered as a bad practice, but you should prevent putting too many business logic in a controller too. If you have this example working, try to read about services so you can define the business logic in a framework-agnostic way. It makes maintaining your application (unit tests, Symfony updates, maybe switch to another framework?) easier.

Controller as Service - How to pass and return values in an advanced case?

Using Symfony, I am displaying a table with some entries the user is able to select from. There is a little more complexity as this might include calling some further actions e. g. for filtering the table entries, sorting by different criteria, etc.
I have implemented the whole thing in an own bundle, let's say ChoiceTableBundle (with ChoiceTableController). Now I would like to be able to use this bundle from other bundles, sometimes with some more parametrization.
My desired workflow would then look like this:
User is currently working with Bundle OtherBundle and triggers chooseAction.
chooseAction forwards to ChoiceTableController (resp. its default entry action).
Within ChoiceTableBundle, the user is able to navigate, filter, sort, ... using the actions and routing supplied by this bundle.
When the user has made his choice, he triggers another action (like choiceFinishedAction) and the control flow returns to OtherBundle, handing over the results of the users choice.
Based on these results, OtherBundle can then continue working.
Additionally, OtherOtherBundle (and some more...) should also be able to use this workflow, possibly passing some configuration values to ChoiceTableBundle to make it behave a little different.
I have read about the "Controller as Service" pattern of Symfony 2 and IMHO it's the right approach here (if not, please tell me ;)). So I would make a service out of ChoiceTableController and use it from the other bundles. Anyway, with the workflow above in mind, I don't see a "good" way to achieve this:
How can I pass over configuration parameters to ChoiceTableBundle (resp. ChoiceTableController), if neccessary?
How can ChoiceTableBundle know from where it was called?
How can I return the results to this calling bundle?
Basic approaches could be to store the values in the session or to create an intermediate object being passed. Both do not seem particularly elegant to me. Can you please give me a shove in the right direction? Many thanks in advance!
The main question is if you really need to call your filtering / searching logic as a controller action. Do you really need to make a request?
I would say it could be also doable just by passing all the required data to a service you define.
This service you should create from the guts of your ChoiceTableBundleand let both you ChoiceTableBundle and your OtherBundle to use the extracted service.
service / library way
// register it in your service container
class FilteredDataProvider
{
/**
* #return customObjectInterface or scallar or whatever you like
*/
public function doFiltering($searchString, $order)
{
return $this->filterAndReturnData($searchString, $order)
}
}
...
class OtherBundleController extends Controller {
public function showStuffAction() {
$result = $this->container->get('filter_data_provider')
->doFiltering('text', 'ascending')
}
}
controller way
The whole thing can be accomplished with the same approach as lipp/imagine bundle uses.
Have a controller as service and call/send all the required information to that controller when you need some results, you can also send whole request.
class MyController extends Controller
{
public function indexAction()
{
// RedirectResponse object
$responeFromYourSearchFilterAction = $this->container
->get('my_search_filter_controller')
->filterSearchAction(
$this->request, // http request
'parameter1' // like search string
'parameterX' // like sorting direction
);
// do something with the response
// ..
}
}
A separate service class would be much more flexible. Also if you need other parameters or Request object you can always provide it.
Info how to declare controller as service is here:
http://symfony.com/doc/current/cookbook/controller/service.html
How liip uses it:
https://github.com/liip/LiipImagineBundle#using-the-controller-as-a-service

How to solve some problems in symfony 2?

I started to explore the world of Symfony 2 now and face with some realy strange problems i would not think they can occure in such a professional framework. I will show you the problems i face one by one:
1) How to get the recent actionName?
I found only this solution which is imho semiprofessional:
$request->attributes->get('_controller');
// will get yourBundle\Controller\yourController::CreateAction
$params = explode('::',$request->attributes->get('_controller'));
// $params[1] = 'createAction';
$actionName = substr($params[1],0,-6);
Is this serious, i have to do some extra-work to get it, why.. Is there a better solution? Creating a base controller class with a method e.g. getActionName(), but why do i have to implement such basic functionality in a framework. Is there a other way?
2) When i forward a request the code in 1) will not work.
$request = $this->container->get('request');
$getParameterList = $request->query->all();
if (!empty($getParameterList['mode'])
&& $getParameterList['mode'] == 1) {
return $this->forward('AcmeDemoBundle:Routing:lawyersearch', array(), $getParameterList);
}
The reason why it will not work is that "AcmeDemoBundle:Routing:lawyersearch" is a other format than when i came directly from a route. Second problem here is that i have to forward the GET-paramters as well(i think POST too). Is there a way that i do not have to care about it?
3) How to use a default template without using this annotation:
/**
* #Template()
*/
public function indexAction()
{
return array();
}
I do not want to have above all my methods this annotation; i know i can put it on the top of the class definition. Is there a way to achieve this? The only solution i see, is to write a BaseController that determines by a method out of the module/controller/action the default template.
4) I found classes that use public attributes e.g. Symfony\Component\Validator\Constraints\Length with e.g. public $max;
How to solve this? Very strange because this is not professional to use public attributes.
I hope someone has easy solutions for this. It would be realy dissapointing if Symfony 2 has so much strange behaviour in so much cases. 4 strange things i 2 days since i began to explore it. It gives me the feeling that there is much more when i continue.
Please confirm that there are no other solution by the framework or which is the solution. Thank you
1) By accessing the '_controller' parameter of the request, you are delving into the internals of Symfony2. They rarely document anything related to this outside of routing. You should use controller actions more definitively, don't try to automate too much on this level.
2) Symfony2 can't account for highly dynamic controllers. You know it is possible to call ->forward more than once, and within the same controller action. This creates a nesting nightmare that the Symfony developers weren't prepared to deal with.
This is one of the reasons $request = $this->container->get('request'); is now deprecated in favour of $stack = $this->container->get('request_stack');. Because forwarding needs to create new internal requests.
3) Also deprecated. Symfony2 best practices now discourages the use of #Template() with empty parameters because of the potentially volatile development of actions/templates. You are supposed to explicitly define which template to use, if you use one at all. This comes in handy when dealing with data-only responses. You wouldn't want your responses to use a template automatically as this would result in unexpected behaviour in your design.
1) Use Constant: __FUNCTION__
http://php.net/manual/en/language.constants.predefined.php
2) Try setMethod on $request:
$this->get('request')->setMethod('POST');
3) I do not know, probably not possible.
4) Symfony\Component\Validator\Constraints\Length is one of constraints:
http://symfony.com/doc/current/book/validation.html#constraints

PHP folder structure for AJAX calls and form actions

I am trying to develope good code organization habits and work exclusively with OOP in php but I can't seem to wrap my head around something.
Here is a simplified description of what I am working with:
I have all my class files in a folder '/resources/Classes'
I have all my html and javascript in '/public_html' & '/public_html/script respectively'
My question is concerning files that are the actions of forms or AJAX requests. For example 'formAction.php' and 'ajaxURL.php'. These files are not Classes and also do not contain any html or other such GUI.
I have been putting them in a folder 'resources/actions' but my gut tells me something about this is not fully OOP.
Is my usage of these files incorrect if I am trying for complete OOP? if so how can I approach this differently.
Here is an actual file from my project as a concrete example:
//file: getBalance.php
<?php
/**
* This script gets the balance of an account from the server
*/
if (!isset($Database)) {
$Database = require_once "../clear_finance_pkg.php";
}
/** #var User $User */
$User = $Database->getUserByID("1");//TODO: user should be set dynamically
$User->setAccounts($Database->getAccountsByUser($User));
if (isset($arg1)) {
$accountID = $arg1;
foreach ($User->getAccounts() as $Account) {
if ($Account->getId() == $accountID) {
$RbcChequing = RbcAccount::accountToRbcAccount($Account, "Chequing");
echo '$' . Money::toDollars($RbcChequing->getBalance());
break;
}
}
} else throw new Exception('Account ID is not set. Could not get balance');
It's difficult to say if your code is complete OOP, but i think it isn't. It looks like you are on the right track, because you are using classes, objects and methods. That's the basic of OOP. No longer large if/else statements and a lot of variables that doesn't make sense, but objects and methods without code-duplication.
I think your question in more related to the seperation of logic and view. First of all. In general it's ok to make a file for a function, but in a large application you will loose the overview. What you are doing know is combine view-related and logic-related things in one file, but actually that's not what you want. The ideal situation is full seperation of logic and view so you can create multiple seperate views based on the same logic.
My advice is to take a look at the MVC-pattern. Take a look at this link. It will help you to get a basic understanding of the MVC-pattern. Notice that you won't longer need to have a file for each function. And you have a seperation of your logic and view elements because you can use the same model in multiple views (although this is maybe not the best example).

Categories