try {
\DB::beginTransaction();
Model::updateOrCreate( ['id' => $id, 'number' => $number],
['value' => $request->get('value')]
);
\DB::commit();
} catch (\Exception $e) {
\DB::rollBack();
throw new \Exception($e->error());
}
I was working on a task to create a common trait to prevent a record to be updated by multiple users at the same time. And, my method was to put hidden input of $data->updated_at in a blade and then to check it when an update request is sent. And there are some cases Laravel's updateOrCreate is used to update or create a new record. And, I don't know how to deal with that. Should, I split the methods to create and update or is there any good way to deal with it?
You probably want to separate what you are doing.
I was playing around and came up with this for fun (haven't tested):
Illuminate\Database\Eloquent\Builder::macro('createOrUpdateWhen', function ($attributes = [], $values = [], $when = null) {
$m = $this->firstOrNew($attributes);
if (is_array($when)) {
$when = function ($m) use ($when) {
foreach ($when as $key => $value) {
if ($m->$key != $value) {
return false;
}
}
return true;
}
}
if (! $m->exists || $when($m)) {
$m->fill($values);
}
$m->save();
return $m;
});
$m = M::createOrUpdateWhen($attributes, $values, function ($m) use ($request) {
return $m->updated_at == $request->input('updated_at');
});
$m = M::createOrUpdateWhen(
$attributes, $values, ['updated_at' => $request->input('updated_at')]
);
:-}
$post = Model::where([['id', $id], ['number', $number]])->first();
try {
\DB::beginTransaction();
if (is_null($post)) {
Model::create($input);
} else {
$post->updateWithExclLock($request->get('updated_at'), $input]);
}
Related
I am trying to do a transaction in the db with Eloquent ORM following the instructions here: https://stackoverflow.com/a/15105781/5649969
I notice that the code throws a Throwable, so I put it in a try...catch block to go for an early return if there is an exception.
try {
DB::transaction(function () use ($user, $attributes) {
$validUserAttributes = $user->getFillable();
$updatable = [];
foreach ($attributes as $key => $value) {
if (in_array($key, $validUserAttributes)) {
$updatable[$key] = $value;
}
}
$user->update($updatable);
$validRoleAttributes = $user->role->getFillable();
$updatable = [];
foreach ($attributes as $key => $value) {
if (in_array($key, $validRoleAttributes)) {
$updatable[$key] = $value;
}
}
$user->role()->update($updatable);
});
} catch (Throwable $_) {
dd(1000);
return new UpdateUserResult(false, UpdateUserResult::UPDATE_FAILED);
}
dd(2000);
return new UpdateUserResult(true, UpdateUserResult::UPDATE_SUCCESS);
This is where my problem is, it seems that the early return does not work for whatever reason, when I remove the dd(2000), dd(1000) will run, why does the code seem like it is running from the bottom to the top?
I am defining a Custom validator in my AppServiceProvider#boot as following
/* Custom unique value in set of fields validation */
Validator::extend('unique_in', function ($attribute, $value, $parameters, $validator) {
$validator_data = $validator->getData();
foreach ($parameters as $field) {
if ($value === array_get($validator_data, $field)) {
return false;
}
}
return true;
}, 'The :attribute cannot be same as any other field in this form.');
/* replace the :fields message string */
Validator::replacer('unique_in', function ($message, $attribute, $rule, $parameters) {
// I was doing this (this works)
// $message = str_replace(':field', implode(',',$parameters), $message);
// return $message;
//I want to do this (to get proper names for the fields)
$other = $this->getAttribute(array_shift($parameters));
return str_replace([':other', ':values'], [$other, implode(', ', $parameters)], $message);
});
Problem is instance of validator is not available to access getAttribute.
getAttribute resolves the readable name for parameters
Is there to access validator instance in replacer?
Note that the closure in Validator::extend has $validator which is an instance of validator.
I got it working using getCustomMessage and setCustomMessage, but without using Validator::replacer using Validator::extend alone.
Validator::extend('unique_in', function ($attribute, $value, $parameters, $validator) {
$validator_data = $validator->getData();
$parameters = array_diff($parameters, [$attribute]);
$same = null;
$other = null;
foreach ($parameters as $field) {
if ($value === array_get($validator_data, $field)) {
$same[] = $field;
}
}
//No same values found , validation success
if (is_null($same)) {
return true;
}
// Get all Custom Attributes those are defined for this validator and replace with field in $same array and create a new $other array
$custom_attributes = $validator->getCustomAttributes();
foreach ($same as $attribute) {
$other[$attribute] = isset($custom_attributes[$attribute]) ? $custom_attributes[$attribute] : $attribute;
}
//Task of Validator:replacer is done right here.
$message = 'The :attribute cannot have same value ":value" as in :fields';
$message = str_replace([':value',':fields'], [$value ,implode(', ', $other)], $message);
$validator->setCustomMessages(array_merge($validator->getCustomMessages(), ['unique_in' => $message]));
return false;
});
Thanks,
K
I know this is a bit old and already answered thread, but the first one I came about when searching for answers to this problem.
I would like to share my solution. I am using Localization with Laravel 5.4 in the Validator::replacer closure like this:
Validator::extend('empty_when', function ($attribute, $value, $parameters) {
foreach ($parameters as $key) {
if ( ! empty(Input::get($key))) {
return false;
}
}
return true;
});
Validator::replacer('empty_when', function ($message, $attribute, $rule, $parameters) {
$fields = [];
foreach ($parameters as $parameter) {
$fields[] = __('validation.attributes.'.$parameter);
}
return str_replace(':values', implode(', ', $fields), $message);
});
Use trans(). You can simply do like this:
Validator::replacer('greater_than', function($message, $attribute, $rule, $parameters) {
return str_replace(':field', trans('validation.attributes.'.$parameters[0]), $message);
});
I have one PHP class as below (part of the code):
class myclass{
private static $arrX = array();
private function is_val_exists($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && $this->is_val_exists($needle, $element))
return true;
}
return false;
}
//the $anInput is a string e.g. Michael,18
public function doProcess($anInput){
$det = explode(",", $anInput);
if( $this->is_val_exists( $det[0], $this->returnProcess() ) ){
//update age of Michael
}
else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
));
}
}
public function returnProcess(){
return self::$arrX;
}
}
The calling code in index.php
$msg = 'Michael,18';
myclass::getHandle()->doProcess($msg);
In my webpage says index.php, it calls function doProcess() over and over again. When the function is called, string is passed and stored in an array. In the next call, if let's say same name is passed again, I want to update his age. My problem is I don't know how to check if the array $arrX contains the name. From my own finding, the array seems to be re-initiated (back to zero element) when the code is called. My code never does the update and always go to the array_push part. Hope somebody can give some thoughts on this. Thank you.
There is a ) missing in your else condition of your doProcess() function, it should read:
else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
)); // <-- there was the missing )
}
Here is a complete running solution based on your code:
<?php
class myclass{
private static $arrX = array();
private function is_val_exists($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && $this->is_val_exists($needle, $element))
return true;
}
return false;
}
//the $anInput is a string e.g. Michael,18
public function doProcess($anInput){
$det = explode(",", $anInput);
if( $this->is_val_exists( $det[0], $this->returnProcess() ) ){
//update age of Michael
for ($i=0; $i<count(self::$arrX); $i++) {
if (is_array(self::$arrX[$i]) && self::$arrX[$i]['name'] == $det[0]) {
self::$arrX[$i]['age'] = $det[1];
break;
}
}
} else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
));
}
}
public function returnProcess(){
return self::$arrX;
}
}
$mc = new myclass();
$mc->doProcess('Michael,18');
$mc->doProcess('John,23');
$mc->doProcess('Michael,19');
$mc->doProcess('John,25');
print_r($mc->returnProcess());
?>
You can test it here: PHP Runnable
As I said in comments, it looks like you want to maintain state between requests. You can't use pure PHP to do that, you should use an external storage solution instead. If it's available, try Redis, it has what you need and is quite simple to use. Or, if you're familiar with SQL, you could go with MySQL for example.
On a side note, you should read more about how PHP arrays work.
Instead of array_push, you could have just used self::$arrX[] = ...
Instead of that, you could have used an associative array, e.g. self::$arrX[$det[0]] = $det[1];, that would make lookup much easier (array_key_exists etc.)
Can you try updating the is_val_exists as follows:
private function is_val_exists($needle, $haystack) {
foreach($haystack as $element) {
if ($element['name'] == $needle) {
return true;
}
return false;
}
I have these two methods in my Contact.php model:
public function getSubscribers($listId)
{
return $this->withTrashed()
->where(DB::raw("concat('',email * 1)"), '!=', DB::raw('email'))
->where('opt_out', '0')
->select('email')
->chunk(1000, function($results) use ($listId) { $this->subscribeEmails($listId, $results); });
}
public function subscribeEmails($listId, $subscribers)
{
$emails = array();
foreach ($subscribers as $key => $subscriber)
{
$memberActivity = $subscriber->memberActivity($listId);
if ( ! $memberActivity['data'])
{
$emails[] = array('email' => $subscriber->email);
}
else
{
foreach ($memberActivity['data'] as $data)
{
foreach ($data['activity'] as $activity)
{
if ($activity['action'] !== 'unsub')
{
$emails[] = array('email' => $subscriber->email);
}
}
}
}
}
MailchimpWrapper::lists()->batchSubscribe($listId, $emails, false, true);
}
And the getSubscribers() method is called in my AdminContactsController.php controller via a method called updateMailchimp():
public function updateMailchimp()
{
$this->contact->getSubscribers($this->listId);
$message = (object) array(
'title' => 'Excellent!',
'content' => 'The Mailchimp newsletter list has been updated with the latest contacts from within the system.',
'alert_type' => 'success'
);
return Redirect::back()->with('message', $message);
}
Locally, this works great, no problems at all but on the staging server, I get the following error referencing the line cotaining ->chunk(1000, function($results) use ($listId) { $this->subscribeEmails($listId, $results); });:
Using $this when not in object context
Is this a PHP version issue or am I missing something here?
The reason why your code works on localhost but not on the remote server is probably the difference in PHP versions. Before PHP 5.4.0 it is not possible to use $this from anonymous function. You must pass the reference to $this within the use keyword:
public function getSubscribers($listId)
{
$that = $this; // <---- create reference to $this
return $this->withTrashed()
->where(DB::raw("concat('',email * 1)"), '!=', DB::raw('email'))
->where('opt_out', '0')
->select('email')
->chunk(1000, function($results) use (&$that, $listId) { $this->subscribeEmails($listId, $results); });
}
PROBLEM
I have a function that takes in a nested array where the structure and nesting of the array is unknown at run-time. All that is known is some of the target fieldnames and desired values of some of the leafs.
QUESTIONS
1) I am hoping to modify this unknown structure and still have the code be readable and easily understood by fellow programmers. What (if any) solution will allow me to do things like this in PHP?
// Pseudo-code for things I would like to be able to do
// this is kinda like the same thing as XPATH, but for native PHP array
// find *every* fname with value of "Brad" and change it to "Brian"
$mydata->find_all('*:fname')->where_value_eq('Brad')->set_equal_to('Brian');
// find *the first* fave_color and set it to "Green"
$mydata->find('*:fave_color')->get(0)->set_equal_to('Green');
2) If there is nothing out there that will let me do this, is there something, anything, that at least comes close to the spirit of what I am hoping to accomplish here?
SAMPLE ARRAY
$mydata = array(
'people' => array(
array('fname'=>'Alice'),
array('fname'=>'Brad'),
array('fname'=>'Chris'),
),
'animals' => array(
array('fname'=>'Dino'),
array('fname'=>'Lassie'),
array('fname'=>'Brad'),
),
'settings' => array(
'user_prefs'=>array(
'localhost'=>array(
'fave_color'=>'blue',
),
),
),
'places' => array(
array('state'=>'New york',
'cities'=>array(
'name'=>'Albany',
'name'=>'Buffalo',
'name'=>'Corning',
),
'state'=>'California',
'cities'=>array(
'name'=>'Anaheim',
'name'=>'Bakersfield',
'name'=>'Carlsbad',
),
),
),
);
Although I maintain that you should stick with explicit manipulation as in my previous answer, boredom and intrigue got the better of me ;)
It probably has holes (and clearly lacks docs) but if you insist on this route, it should get you started:
class Finder {
protected $data;
public function __construct(&$data) {
if (!is_array($data)) {
throw new InvalidArgumentException;
}
$this->data = &$data;
}
public function all() {
return $this->find();
}
public function find($expression = null) {
if (!isset($expression)) {
return new Results($this->data);
}
$results = array();
$this->_find(explode(':', $expression), $this->data, $results);
return new Results($results);
}
protected function _find($parts, &$data, &$results) {
if (!$parts) {
return;
}
$currentParts = $parts;
$search = array_shift($currentParts);
if ($wildcard = $search == '*') {
$search = array_shift($currentParts);
}
foreach ($data as $key => &$value) {
if ($key === $search) {
if ($currentParts) {
$this->_find($currentParts, $value, $results);
} else {
$results[] = &$value;
}
} else if ($wildcard && is_array($value)) {
$this->_find($parts, $value, $results);
}
}
}
}
class Results {
protected $data;
public function __construct(&$data) {
$this->data = $data;
}
public function get($index, $limit = 1) {
$this->data = array_slice($this->data, $index, $limit);
return $this;
}
public function set_equal_to($value) {
foreach ($this->data as &$datum) {
$datum = $value;
}
}
public function __call($method, $args) {
if (!preg_match('/^where_?(key|value)_?(eq|contains)$/i', $method, $m)) {
throw new BadFunctionCallException;
}
if (!isset($args[0])) {
throw new InvalidArgumentException;
}
$operand = $args[0];
$isKey = strtolower($m[1]) == 'key';
$method = array('Results', '_compare' . (strtolower($m[2]) == 'eq' ? 'EqualTo' : 'Contains'));
$ret = array();
foreach ($this->data as $key => &$datum) {
if (call_user_func($method, $isKey ? $key : $datum, $operand)) {
$ret[] = &$datum;
}
}
$this->data = $ret;
return $this;
}
protected function _compareEqualTo($value, $test) {
return $value == $test;
}
protected function _compareContains($value, $test) {
return strpos($value, $test) !== false;
}
}
$finder = new Finder($mydata);
$finder->find('*:fname')->where_value_eq('Brad')->set_equal_to('Brian');
$finder->find('*:fave_color')->get(0)->set_equal_to('Green');
$finder->find('places:*:cities:*:name')->where_value_contains('ba')->set_equal_to('Stackoton');
print_r($mydata);
There's certainly no native solution for this and the syntax is rather strange. If you want the code to "be readable and easily understood by fellow programmers" please stick to methods that we're used to working with ;)
foreach ($mydata as $type => &$data) {
foreach ($data as &$member) {
if (isset($member['fname']) && $member['fname'] == 'Brad') {
$member['fname'] = 'Brian';
}
}
}
It's admittedly more verbose, but there's much less chance of confusion.