I'm playing around with some different design patterns to teach myself more about them, and started using DI Containers.
Main Code (index.php)
$container = new \League\Container\Container();
$container->add("config", function(){
return new Config(APP_ROOT . "/config.json");
});
$container->add("GoogleBooks", GoogleBooksProvider::class)
->withArgument( $container['config'] );
$container->add("books", BookRepository::class);
// empty array, as expected
var_dump($container['books']->getProviders());
// this line doesn't add the provider
$container['books']->addProvider( $container['GoogleBooks'] );
// empty array, should expect to have one entry, GoogleBooksProvider
var_dump($container['books']->getProviders());
BookRepository::addProvider
public function addProvider( iProvider $provider ) {
$this->_providers->push($provider);
return $this;
}
That doesn't work as expected, problem described in the code comments. However, if I swap
$container['books']->addProvider( $container['GoogleBooks'] );
with
$container['books'] = $container['books']->addProvider( $container['GoogleBooks'] );
it works correctly, by storing the GoogleBooksProvider in the BookRepository. Why do I need an assignment operator to make that work correctly?
If I do it without putting it in the container, it works as I expected, without the assignment operator.
$br = new BookRepository();
$br->addProvider( new GoogleBooksProvider($container['config']) );
// shows GoogleBooks is in the _providers array
var_dump($br->getProviders());
Since you are adding, but not sharing the service, you fetch a new instance of BookRepository every time you access the container:
var_dump($container['books'] === $container['books']); // false
If you want to share BookRepository, you need to use this:
$container->add("books", BookRepository::class, true);
or (shorter)
$container->singleton("books", BookRepository::class);
Related
I need to be able to loop through a list of laravel request variables and do something with them. I want to be able to use a variable when calling the request object so that I can run it in a loop instead of writing a line of code for every one.
For example, my text inputs may have names that look something like this
contact_main_name
contact_main_telephone
contact_main_email
contact_sub_name
contact_sub_telephone
contact_sub_email
contact_backup_name
contact_backup_telephone
contact_backup_email
In my request, I don't want to have to write
$request->contact_main_name
$request->contact_main_telephone
For each different type of contact I may have, I want to be able to loop through them like so
$contactTypes = [
'main',
'sub',
'backup',
'head'
];
foreach($contactTypes as $type){
//Start a new contact
$contact = new Contact;
$contact->type = $type;
$contact->name = $request->${"contact_".$type."_name"};
$contact->telephone = $request->${"contact_".$type."_telephone"};
$contact->email = $request->${"contact_".$type."_email"};
$contact->save();
}
How would i use a variable name when calling a laravel $request so that I can just build an array of possible types and loop through them all?
Note
I know i can edit the input fields themselves to look something like name="contact[type][name]" and then loop through them, but I cant be changing the input names, I have to do it via php in the controller itself.
As answered in comments, to do this, change the method of calling the input and use the actual input() function itself.
$contactTypes = [
'main',
'sub',
'backup',
'head'
];
foreach($contactTypes as $type){
//Start a new contact
$contact = new Contact;
$contact->type = $type;
$contact->name = $request->input("contact_".$type."_name");
$contact->telephone = $request->input("contact_".$type."_telephone");
$contact->email = $request->input("contact_".$type."_email");
$contact->save();
}
As an aside, you could also modify it slightly to use array indices matching the field names; this would allow you to add fields later by adding the appropriate field to the database and HTML without touching the code, and use array_keys() to retrieve the types submitted to allow seamless addition of types. As long as your validations are tight, this is probably the most automated way to allow future expansion...
Ex. Field Names:
contact[main][name]
contact[main][telephone]
...
contact[backup][email]
Ex. Code:
foreach(array_keys($request->input('contact')) as $type) {
$contact = Contact::create($request->input('contact.'.$type));
$contact->type = $type;
$contact->save();
}
Can anyone advice how best to set a temporary variable with scope to be used between hooks?
I have a custom module with the purpose of checking and warning if an attempted file upload matches an existing file on the system.
I am using hook_form_alter to identify the specific upload form and hook_file_validate to check if the file was previously uploaded.
My problem is those two hooks don't share a common parameter - I need to pass information from hook_form_alter back to hook_validate.
The information I want the functions to share is a simple boolean which should be destroyed immediately the file upload is done/dismissed, so using variable_set to persist it to the database is overkill. I don't believe a session or cookie approach is best either.
UPDATES:
Globals approach:
function duplicate_uploads_warning_init(){
$GLOBALS['processed'] = 'testing';
}
function duplicate_uploads_warning_file_validate($file){
drupal_set_message("PROCESSED[file-validate]: {$GLOBALS['processed']}", 'warning');
}
function duplicate_uploads_warning_form_alter(&$form, &$form_state, $form_id){
if( $form_id == 'file_entity_add_upload' ){
drupal_set_message("PROCESSED[form-alter]: {$GLOBALS['processed']}", 'error');
$GLOBALS['processed'] = 'tested';
$form['#validate'][] = 'duplication_validate';
}
}
The code above sets GLOBALS[processed] in the init hook and that value in immediately confirmed in the hook_form_alter.
However the attempt to reassign the value to tested fails. The reassigned value is what I hoped to see in hook_file_validate but I still get the initial value of testing.
Hook_form_alter validation approach:
I tried adding a custom validation function but the upload of the image still took place where I intend to stop it. My code is as follows:
function duplication_validate($form, &$form_state) {
$data = duplicates($form_state['complete form']['upload']['#file']->filename);
if( sizeof($data) > 0 ){
form_set_error('test', 'testing validation');
return false;
}
}
I can confirm my $data variable has content and the sizeof test returns greater than 0.
using variable_set to persist it to the database is overkill
I don't agree they are overkill (you can easily delete variables), and as #2pha have mentioned globals are not recommended. I think you could use variable_set($name, $value), variable_get($name), and variable_del($name) without worrying unless you need to super-optimise your sites database queries. If you really don't want to use the variable_* functions, then maybe cache_set() and cache_get() might work because you can give it a temporary status.
Edit, variables approach:
function duplicate_uploads_warning_init(){
$account = \Drupal::currentUser();
variable_set($account->id() . '_processed', 'testing');
}
function duplicate_uploads_warning_file_validate($file){
$account = \Drupal::currentUser();
$processed_var = variable_get($account->id() . '_processed');
drupal_set_message("PROCESSED[file-validate]: {$processed_var}", 'warning');
}
function duplicate_uploads_warning_form_alter(&$form, &$form_state, $form_id){
if( $form_id == 'file_entity_add_upload' ){
$account = \Drupal::currentUser();
$processed_var = variable_get($account->id() . '_processed');
drupal_set_message("PROCESSED[form-alter]: {$processed_var}", 'error');
variable_set($account->id() . '_processed', 'tested');
$form['#validate'][] = 'duplication_validate';
}
}
… and in a success or #submit callback function run variable_del($account->id() . '_processed') to delete the variable.
maybe $GLOBALS['processed'] was initialized with the static keyword elsewhere....?
I meet a strange error when I'm coding my application, I would like to use a parameter in url to change the class declaration of an object in a function, I explain my point of view with code, I have product page :
/Product?Page=One
Then I do like that to declare an object in product.php :
public function product() {
$Pagenumber = $_GET['Page']
if(isset($Pagenumber) && trim($Pagenumber) == 'One'){
var_dump('test') // give me "test" , and that mean it's OK!
$object = new class1()
}
if(isset($Pagenumber) && trim($Pagenumber) == 'Two'){
$object = new class2()
}
//then in the first use of object I got an error
...
}
The error is :
Notice: Undefined variable: object
I tried a lot of possibilities but I got nothing , moreover, I got a strange thing when I do like that, it's works very fine :
$foo = "One"; // string
$Pagenumber = "One";
settype($foo, "string"); // $foo vaut maintenant 5 (integer)
settype($Pagenumber, "string");
if($Pagenumber == $foo){
$object = new class1()
}
$object // I can use the object with no error
...
and here I remark that the problem is with parameter in link only?
Someone have a better solution to change class declaration with value of parameter URL or any better solution of issue above?
From your url it looks like you are using url rewriting:
/Product?Page=One
opens:
product.php
If you use .htaccess for that, you need to make sure you add the QSA flag to insure that existing parameters get added to the rewritten url as well.
Apart from that you should make sure that $object is always set or add error handling when it does not match any of your conditions.
It would help you provide the line of code where the variable $object is generating the notice, but a guess would be that you wrote $$object instead of $object
I'm developing a Joomla 3.x plugin, and want to be able to change the plugin parameter set in the plugin's manifest file programmatically. I believe I need to use a JRegistry object, but I'm not sure about the syntax.
Here's the issue:
// token A is set in plugin params as defined in plugin's XML manifest
var_dump($this->params->get('token')); // prints token "A" as expected
// do some stuff to get a fresh access token, called token "B"
$tokenB = $function_to_get_fresh_token();
// set the new token
if ($tokenB) $this->params->set('token', $tokenB);
var_dump($this->params->get('apptoken')); // prints token "B" as expected
the problem is that on subsequent page reloads, the token reverts to tokenA rather than what I assumed would be the stored value of tokenB.
How do I store the tokenB value in the plugin's parameters in the database?
This is a working example of how to change plugin params from within the plugin (J! 3.4):
// Load plugin called 'plugin_name'
$table = new JTableExtension(JFactory::getDbo());
$table->load(array('element' => 'plugin_name'));
// Params can be changed like this
$this->params->set('new_param', 'new value'); // if you are doing change from a plugin
$table->set('params', $this->params->toString());
// Save the change
$table->store();
Note: If new params are added by plugin dynamically and the plugin is saved afterwards, these new params gets deleted. So one way to deal with it is to add those params as hidden fields to plugin's config XML.
This is just an outline, but something along these lines
$extensionTable = new JtableExtension();
$pluginId = $extensionTable->find('element', 'my_plugin');
$pluginRow = $extensionTable->load($pluginId);
// Do the jregistry work that is needed
// do some stuff to get a fresh access token, called token "B"
$tokenB = $function_to_get_fresh_token();
// set the new token
if ($tokenB) $this->params->set('token', $tokenB);
// more stuff
$extensionTable->save($pluginRow);
I spent a lot of time googling and reading and found no real answer to this. Oddly enough this doesn't seem to have been provided for in Joomla. So here's what I ended up doing:
1) build a function to get your plugin ID, since it will change from one installation to another
private function getPlgId(){
// stupid hack since there doesn't seem to be another way to get plugin id
$db = JFactory::getDBO();
$sql = 'SELECT `extension_id` FROM `#__extensions` WHERE `element` = "my_plugin" AND `folder` = "my_plugin_folder"'; // check the #__extensions table if you don't know your element / folder
$db->setQuery($sql);
if( !($plg = $db->loadObject()) ){
return false;
} else {
return (int) $plg->extension_id;
}
}
2) use the plugin id to load the table object:
$extension = new JTableExtension($db);
$ext_id = $this->getPlgId();
// get the existing extension data
$extension->load($ext_id);
3) when you're ready to store the value, add it to the params, then store it:
$this->params->set('myvalue', $newvalue);
$extension->bind( array('params' => $this->params->toString()) );
// check and store
if (!$extension->check()) {
$this->setError($extension->getError());
return false;
}
if (!$extension->store()) {
$this->setError($extension->getError());
return false;
}
If anyone knows a better way to do this please let me know!
I have an element. I want to add a custom validator and custom filter to it. The validator makes sure the input is one of several permitted values, then the filter adds some custom values to the input. This means I have to validate the original input first before running the filter. I do it in this order
$element = new Zend_Form_Element_Text('element');
$element->addValidator('PermittedValue', false);
$element->addFilter('TotalHyphen', false);
$this->addElement($element);
but this order isn't being respected. The filter runs first and changes the data, then the validator runs on the filtered data which means it always fails even for valid input. It seems from documentation that this is intentional
Note: Validation Operates On Filtered
Values Zend_Form_Element::isValid()
filters values through the provided
filter chain prior to validation. See
the Filters section for more
information.
How can I specify the order in which validators and filters run?
Sure seems like creating a custom element that supports post-validation filtering would be the way to go. How about this:
/**
* An element that supports post-validation filtering
*/
class My_Form_Element_PostValidateFilterable extends Zend_Form_Element_Text
{
protected $_postValidateFilters = array();
public function setPostValidateFilters(array $filters)
{
$this->_postValidateFilters = $filters;
return $this;
}
public function getPostValidateFilters()
{
return $this->_postValidateFilters;
}
public function isValid($value, $context = null)
{
$isValid = parent::isValid($value, $context);
if ($isValid){
foreach ($this->getPostValidateFilters() as $filter){
$value = $filter->filter($value);
}
$this->setValue($value);
}
return $isValid;
}
}
Usage would be something like this:
$elt = $form->addElement('PostValidateFilterable', 'myElement', array(
'label' => 'MyLabel',
'filters' => array(
'StringTrim',
// etc
),
'validators' => array(
'NotEmpty',
// etc
),
// here comes the good stuff
'postValidateFilters' => array(
new My_Filter_RunAfterValidateOne(),
new My_Filter_RunAfterValidateTwo(),
),
));
This keeps the validation and filtering in the form - keeping the controller thin.
Not tested, just a stab in the dark. And surely you could fatten/modify the API to add/remove filters by key, etc.
Whaddya think?
Maybe don't add the filter at all. Validate the content first in the controller, and then use the filter separately:
$request = $this->getRequest();
if ($request->isPost() && $form->isValid($request->getParams())) {
$filter = new Filter_Whatever();
$val = $filter->filter($request->getParam('element'));
... //call your model or whatever
}
I've never done this, but I suppose this (or something similar) might work.
Good point ! ,
AFAIK filters should or must run before validating the input :
from ZF docs
It's often useful and/or necessary to
perform some normalization on input
prior to validation. For example, you
may want to strip out all HTML, but
run your validations on what remains
to ensure the submission is valid. Or
you may want to trim empty space
surrounding input so that a
StringLength validator will use the
correct length of the input without
counting leading or trailing
whitespace characters.
but if and only if you are in case which can't solve mingos's answer must be the help
What you want to achieve is to change default behavior of how text element is being processed. Thus, I think you could create your own element (e.g. My_Form_Element_Text) that extends Zend_Form_Element_Text and overload its isValid() method.
Specifically you could just change second line in the orginal isValid() method, from $value = $this->getValue(); into $value = $this->getUnfilteredValue();. This way your validation will be performed using unfiltered values.