Right way of validating xlsx files before database inserting - php

while playing with PHPExcel I came across some questions how properly handle validation/and inserting values into a database. I do not need any codes, just the general concept how to do it.
Firstly I iterate through first row to check if the columns are matching the given one ( if it fits the schema ).
On the next step, I get the rows and meanwhile its beeing validated row/column wise. If the type doesn't match I will get an error.
While validating the row, I need to get the Worker name and convert it to id get_worker_id().
Question number #1.
Is such solution a good practice? It will produce upto 100 queries. Foreach row - 1.
Question number #2
I also need to validate the rows once again, I would take the worker_id, the F and G column to check if such record isn't present in the database. I would simply introduce a function similar to get_worker_id() but it would return true/false if entry exists.
But again is this the proper way of doing it? By raw calculations my method would produce 100 selects ( get_worker_id ), 100 selects ( validate if exists ), 100 insert ( if all is ok ).
Im not sure if I am doing it properly. Could you hit me up with some advices?
Thanks in forwards.
Model for handling the xlsx file.
class Gratyfikant_model extends CI_Model {
private $_limit = 100;
const columns = array(
'A' => "Z",
'B' => "KS",
'C' => "G",
'D' => "S",
'E' => "Numer",
'F' => "Miesiąc", // required
'G' => "Data wypłaty", // required
'H' => "Pracownik", // required
'I' => "Brutto duże", // required
'J' => "ZUS pracownik", // required
'K' => "ZUS pracodawca", // required
'L' => "Do wypłaty", // required
'M' => "Obciążenie", // required
'N' => "FW");
const validators = array(
'F' => 'date',
'G' => 'date',
'H' => 'string',
'I' => 'float',
'J' => 'float',
'K' => 'float',
'L' => 'float',
'M' => 'float',
);
const validators_errors = array(
'float' => "Wartość nie jest liczbą",
'string' => "Wartość nie jest poprawna",
'date' => "Wartość nie jest datą"
);
protected $_required = array(
'H', 'I', 'J', 'K', 'L', 'M'
);
private $_sheet = array();
private $_sheet_pracownicy = array();
private $_agregacja = array();
protected $_invalid_rows = array();
public function __construct() {
parent::__construct();
}
public function read_data(array $dane) {
if (count($dane) > $this->_limit) {
throw new Exception('Limit wierszy to ' . $this->_limit);
}
$this->_sheet = $dane;
return $this;
}
public function column_validation() {
foreach ($this->_required as $r) {
if (!isset($this->_sheet[1][$r]) || $this->_sheet[1][$r] != self::columns[$r] || !array_key_exists($r, $this->_sheet[1])
) {
throw new Exception('Kolumna - ' . $r . ' - Wartość nagłówka nie pasuje do szablonu, powinno być ' . self::columns[$r]);
}
}
return $this;
}
function validateDate($date) {
$d = DateTime::createFromFormat('Y-m-d', $date);
return $d && $d->format('Y-m-d') === $date;
}
private function row_validation($k, $a, $v, $f) {
switch ($v) {
case "date":
$cellval = $this->validateDate(PHPExcel_Style_NumberFormat::toFormattedString($f, PHPExcel_Style_NumberFormat::FORMAT_DATE_YMD));
break;
case "float":
$cellval = is_float($f);
break;
case "string":
$cellval = is_string($f);
break;
default:
break;
}
if (!$cellval) {
$this->_invalid_rows[$a][$k] = $v;
}
}
public function get_sheet_data() {
$dane = $this->_sheet;
unset($dane[1]); // remove first col
$zus_pracownik = 0;
$zus_pracodawca = 0;
$zus_lacznie = 0;
$do_wyplaty = 0;
$obciazenie = 0;
$brutto = 0;
foreach ($dane as $a => $d) {
foreach (self::validators as $k => $v) {
echo $this->row_validation($k, $a, $v, $d[$k]);
}
if (!is_null($d["H"]) && !empty($d["H"])) {
// $this->_sheet_pracownicy[$d["H"]]["numer"] = PHPExcel_Style_NumberFormat::toFormattedString($d["E"], PHPExcel_Style_NumberFormat::FORMAT_DATE_DDMMYYYY);
$this->_sheet_pracownicy[] = array(
"pracownik" => $d["H"],
"miesiac" => PHPExcel_Style_NumberFormat::toFormattedString($d["F"], PHPExcel_Style_NumberFormat::FORMAT_DATE_YMD),
"data_wyplaty" => PHPExcel_Style_NumberFormat::toFormattedString($d["G"], PHPExcel_Style_NumberFormat::FORMAT_DATE_YMD),
"zus_pracownik" => $d["J"],
"zus_pracodawca" => $d["K"],
"zus_lacznie" => bcadd($d["K"], $d["J"]),
"do_wyplaty" => $d["L"],
"obciazenie" => $d["M"],
"brutto" => $d["I"],
"id_prac" => $this->get_worker_id($d["H"]));
$zus_pracownik = bcadd($zus_pracownik, $d["J"]);
$zus_pracodawca = bcadd($zus_pracodawca, $d["K"]);
$zus_lacznie = bcadd($zus_lacznie, bcadd($d["K"], $d["J"]));
$do_wyplaty = bcadd($do_wyplaty, $d["L"]);
$obciazenie = bcadd($obciazenie, $d["M"]);
$brutto = bcadd($brutto, $d["I"]);
}
}
$this->_agregacja = array(
"zus_pracownik" => $zus_pracownik,
"zus_pracodawca" => $zus_pracodawca,
"zus_lacznie" => $zus_lacznie,
"do_wyplaty" => $do_wyplaty,
"obciazenie" => $obciazenie,
"brutto" => $brutto
);
return $this;
}
public function display_result() {
if (empty($this->_invalid_rows)) {
return array(
"wartosci" => $this->_sheet_pracownicy,
"agregacja" => $this->_agregacja
);
}
}
public function display_errors() {
foreach ($this->_invalid_rows as $k => $a) {
foreach ($a as $key => $value) {
throw new Exception('Pole ' . $key . '' . $k . ' ' . self::validators_errors[$value]);
}
}
return $this;
}
public function get_worker_id($getAd) {
$this->db->select('id_pracownika as id')
->from('pracownicy')
->like('CONCAT( imie, \' \', nazwisko )', $getAd)
->or_like('CONCAT( nazwisko, \' \', imie )', $getAd);
$query = $this->db->get();
$result = $query->result_array();
if (isset($result[0]["id"])) {
return $result[0]["id"];
} else {
throw new Exception('Nie odnaleziono ' . $getAd . ' w bazie danych, proszę dodać pracownika a następnie ponownie wczytać plik');
}
}
}
Display
try {
$data['s'] = $this->gm
->read_data($sheetData)
->column_validation()
->get_sheet_data()
->display_errors()
->display_result();
} catch (Exception $e) {
$data['ex'] = $e->getMessage();
}
XLSX file example
+---+---------------+---+---+------------+---------+--------------+-----------+-------------+---------------+----------------+------------+------------+--------+
| Z | KS | G | S | Numer | Miesiąc | Data wypłaty | Pracownik | Brutto duże | ZUS pracownik | ZUS pracodawca | Do wypłaty | Obciążenie | FW |
+---+---------------+---+---+------------+---------+--------------+-----------+-------------+---------------+----------------+------------+------------+--------+
| | nieprzekazany | G | | 03.08.2017 | sie.17 | 08.09.2017 | Worker1 | 2000 | 274,2 | 392,2 | 1459,48 | 2392,2 | (brak) |
| | nieprzekazany | G | | 03.08.2017 | sie.17 | 08.09.2017 | Worker2 | 1000 | 137,1 | 171,6 | 768,24 | 1171,6 | (brak) |
| | nieprzekazany | G | | 03.08.2017 | sie.17 | 08.09.2017 | Worker3 | 2000 | 274,2 | 392,2 | 1413,88 | 2392,2 | (brak) |
| | nieprzekazany | G | | 03.08.2017 | sie.17 | 08.09.2017 | Worker4 | 2000 | 274,2 | 392,2 | 1418,88 | 2392,2 | (brak) |
+---+---------------+---+---+------------+---------+--------------+-----------+-------------+---------------+----------------+------------+------------+--------+

This really depends on the scale of your application and how frequently this Excel file will be imported. For example, if your application receives little to no traffic then running several queries per line is not the end of the world. If you already have the server and database setup and running then you might as well make use of them. Conversely, if your application is under constant heavy load then trying to minimize the amount of queries you run may be a good idea.
Option 1
If your application is small and/or doesn't get much traffic then don't worry about the ~300 queries you need to make. MySQL is not fragile and if you have indexed your data well your queries will be very fast.
Option 2
Move to querying the data you need first and storing it in memory so you can perform your logic checks in PHP.
This means for Question 1 you should get all of your workers in one query and then build a lookup array in PHP.
Here is a very rough example:
// Get all workers
SELECT worker_name, worker_id FROM workers;
// Build a lookup array from the query results
$worker_array = array(
'Worker1' => 1,
'Worker2' => 2,
...
);
// Then as you loop each row check if the work is in your lookup array
if ( ! isset($worker_array[$excel_row['worker_name']])) {
// do something
}
Likewise for Question 2 you could get your unique data samples in one query (you don't need the entire record, just the unique fields). However, this may present a problem if you have a lot of unique data samples.
Option 3
Create a temporary table in MySQL and import your Excel data without performing any logic checks. Then you can perform your logic checks entirely in SQL.
Here is a very rough example without knowing anything about your data structure:
-- Get all records in the Excel data that match unique data samples
SELECT
*
FROM
temporary_table tt
JOIN
workers w
ON w.worker_name=tt.worker_name
JOIN
data d
ON d.worker_id=w.worker_id
AND d.col_f=tt.col_f
AND d.col_g=tt.col_g
If there are no issues with the data then you can perform an INSERT from your temporary table into your data table. This limits your queries to the initial insert (you can batch this for better performance as well), the data check and the insert from temp to real data.
Recap
It all comes down to your application. If you can get away with doing Option 1 and you've already got it implemented then that's fine for now. You don't need to over optimize things if you don't see this application growing like crazy.
However, if you are worried about scale and growth then I'd personally look at implementing Option 3.

There are multiple concerns here:
Split import into stages.
Validate headers. (Break importing if errors found) 2) Iterate
over each row.
Validate row.
Import if valid.
Log error if any.
Stop processing file if all rows where processed or go back to 2.
As to weather you need some chunking it depend on how much time and memory your script is consuming. If you need it, it's as simple as reading X rows to memory and then processing it. At extreme you can load each record separately.
If you do not need it just load it all to array.
chunking - consuming up to X rows in a single iteration, then clearing memory then consuming next chunk...

100 queries just doesn't sound right to me for a single php instance.
https://secure.phabricator.com/book/phabcontrib/article/n_plus_one/
Or just search for n plus 1 query problem

Related

Design patterns to override attributes of object and validate them

GOAL:
I want to build a "market validation control engine" that validates players' actions and limits on the market.
SUMMARY:
Consider that we have a Character object that can buy only a given number of Item objects, e.g Arrow. The number depends on Race, Tire and Area where a lower layout of hierarchy overrides the upper; if the limit isn't specified lower layout inherits it from the upper:
For example:
Pro ELF in Forest can buy up to 2000 arrows
Basic Ork in Forest and Basic Ork in Desert can buy 50 arrows.
Additionally, the Character object can Buy and Sell a specific amount of arrows on the market. The amount and action depend on the Area. Otherway to say is every node might have its validation rules.
For example:
Basic Elf in Desert can sell 50% arrows
Basic Orc in Desert can sell 100% of the arrows.
Lastly, based on the Tier players can Exchange arrows to another item. For example :
Pro ELF can exchange 100% of its arrows
STRUGGLES:
Building hierarchy
Validation rules. Every layout of hierarchy (Tier or Area) have rules
How to make 1 and 3 work together
Scalability. Adding a new layout (e.g. Clan) or attaching a new validation rule to Area
Maintenance
I am almost sure that I need multiple different patterns here. Currently, I'm thinking of the Decorator Pattern for creating hierarchy object with all the limits and the Chain of Responsibility for the rule validation. However, I am not sure how well these patterns will work together in the long ran.
UPD: In the example, I used Arrow - one of the hundreds of items on the market and Desert with Forest - two of the 9 areas.
There's quite a few different ways to implement this so I'm assuming you're not really sure where to start.
I've written up some example code using classes. In this, there's 2 ways that we could store the item (arrow) data.
Nested Array - This allows for a more "structured" approach. It also allows you to easily add more attributes to each "child" array if necessary.
Serialized Map - This has a much smaller code footprint and is generally quicker.
NOTE: This code is not setup to be well-performing (in reference to speed) but as a way to see how you could setup this system.
There are 3 files here index.php, Character.class.php, Market.class.php.
There are 2 main methods on the Market class: getNestedLimit() and getSerializedLimit(). Each one shows how you would get the limit based on those example structures.
<?php
// index.php
require_once __DIR__ . "/Character.class.php";
require_once __DIR__ . "/Market.class.php";
$market = new Market;
$orc_a = new Character("orc", "basic", "desert");
$orc_b = new Character("orc", "pro", "forest");
$market->getNestedLimit($orc_a, "arrow"); // 50
$market->getNestedLimit($orc_b, "arrow"); // 150
$elf_a = new Character("elf", "pro", "forest");
$elf_b = new Character("elf", "pro", "desert");
$market->getSerializedLimit($elf_a, "arrow"); // 2000
$market->getSerializedLimit($elf_b, "arrow"); // 50
<?php
// Character.class.php
class Character {
public string $race;
public ?string $tier = null;
public ?string $area = null;
public function __construct(
string $race,
?string $tier = null,
?string $area = null,
) {
$this->race = $race;
$this->tier = $tier;
$this->area = $area;
}
}
// Market.class.php
class Market {
public $nested_items = [
"arrow" => [
"limit" => [
"_base" => 0,
"race" => [
"orc" => [
"_base" => 50,
"tier" => [
"basic" => [
"area" => [],
],
"pro" => [
"area" => [
"desert" => 50,
"forest" => 150,
],
],
],
],
"elf" => [
"_base" => 1000,
"tier" => [
"basic" => [
"area" => [
"desert" => 150,
"forest" => 1000,
],
],
"pro" => [
"area" => [
"desert" => 50,
"forest" => 2000,
],
],
],
],
],
],
],
];
public function getNestedLimit(Character $character, string $item_id) {
$item = $this->nested_items[$item_id];
$item_limit = $item["limit"];
$actual_limit = $item_limit["_base"];
// check race
if (isset($item_limit["race"][$character->race])) {
$race_limit = $item_limit["race"][$character->race];
if (isset($race_limit["_base"])) {
// value exists - use this
$actual_limit = $race_limit["_base"];
}
// check tier
if (isset($race_limit["tier"][$character->tier])) {
$tier_limit = $race_limit["tier"][$character->tier];
if (isset($tier_limit["_base"])) {
// value exists - use this
$actual_limit = $tier_limit["_base"];
}
// check area
if (isset($tier_limit["area"][$character->area])) {
$actual_limit = $tier_limit["area"][$character->area];
}
}
}
return $actual_limit;
}
/** Serialize syntax:
* {race}[-{tier}[-{area}]]
* e.g.
* "Pro tier orc in forest" -> "o-p-f"
* "Basic tier elf" -> "e-b"
* "Orc" -> "o"
*
* When determining the limit, pick the most specific first.
*/
public $serialized_items = [
"arrow" => [
"limit" => [
"_base" => 0,
// orc
"o" => 50,
"o-p-d" => 50,
"o-p-f" => 150,
// elf
"e" => 1000,
"e-b-d" => 150,
"e-b-f" => 1000,
"e-p-d" => 50,
"e-p-f" => 2000,
]
]
];
/** This "map" is purely for example - you may end up using ids
* or something more consistent.
* I would recommend using a different system, such as enums or constants.
*/
public $serial_keys = [
"orc" => "o",
"elf" => "e",
"basic" => "b",
"pro" => "p",
"desert" => "d",
"forest" => "f",
];
public function getSerializedLimit(Character $character, string $item_id) {
$item = $this->serialized_items[$item_id];
$item_limit = $item["limit"];
$actual_limit = $item_limit["_base"];
// serialize character
// this could be done in various ways - this is purely for example
// start with most specific
$serial = $this->serial_keys[$character->race] . "-" . $this->serial_keys[$character->tier] . "-" . $this->serial_keys[$character->area];
while ($serial !== "") {
if (isset($item_limit[$serial])) {
$actual_limit = $item_limit[$serial];
break;
}
// remove most specific section
// here we are expected each section to be a single character
// be removing 2 characters, we remove the hyphen as well
// NOTE: this hyphen is only here to help visualize
// you could very easily use 1 character without
// the hyphen and change this to `-1`
$serial = substr($serial, -2);
}
return $actual_limit;
}
}
EDIT: Adding example compiling nested array
As mentioned in the comments, you won't have to manually adjust the array - instead you could have it compile on load/initialise of the market. The code below shows an example datasource setup and code to compile the nested array.
This can also easily be adjusted to re-created the serialized array.
<?php
Datasource: `item`
| item_id | name | limit |
|---------|-------|-------|
| arrow | Arrow | 0 |
Datasource: race
| race_id | `name` |
|---------|--------|
| elf | Elf |
| orc | Orc |
Datasource: `tier`
| tier_id | name |
|---------|-------|
| basic | Basic |
| pro | Pro |
Datasource: `area`
| area_id | name |
|---------|--------|
| forest | Forest |
| desert | Desert |
Datasource: `item_limit`
| item_id | race_id | tier_id | area_id | limit |
|---------|---------|---------|---------|-------|
| arrow | orc | NULL | NULL | 50 |
| arrow | orc | pro | forest | 150 |
| arrow | elf | NULL | NULL | 1000 |
| arrow | elf | basic | desert | 150 |
| arrow | elf | pro | desert | 50 |
| arrow | elf | pro | forest | 2000 |
// Using these - compile to nested array (i.e. you only need to maintain the datasources, not the array)
$items = ... // results from `item` datasource;
$races = ... // results from `race` datasource;
$tiers = ... // results from `tier` datasource;
$areas = ... // results from `area` datasource;
$nested_array = [];
foreach($items as $item){
$limit_data = ["_base" => $item["limit"]];
// populate races
$limit_data["race"] = [];
foreach($races as $race){
$race_data = ["tier" => []];
// populate tiers
foreach($tiers as $tier){
$tier_data = ["area" => []];
foreach($areas as $area){
$tier_data["area"][$area["area_id"]] = [];
}
$race_data["tier"][$tier["tier_id"]] = $tier_data;
}
$limit_data["race"][$race["race_id"]] = $race_data;
}
$item_limits = ... // results from `item_limit` datasource filtered by `item_id`
foreach($item_limits as $item_limit){
// expects "less specific" columns to always be set
// i.e. if `area` is set, `tier` and `race` should be too
// assigning variable by reference to keep this example short
$parent =& $limit_data["race"][$item_limit["race_id"]];
if(!isset($item_limit["tier_id"])){
$parent["_base"] = $item_limit["limit"];
continue;
}
$parent =& $parent["tier"][$item_limit["tier_id"]];
if(!isset($item_limit["area_id"])){
$parent["_base"] = $item_limit["limit"];
continue;
}
$parent["area"][$item_limit["area_id"]] = $item_limit["limit"];
}
$nested_array[$item["item_id"]] = ["limit" => $limit_data];
}
Memory Considerations
The above code shows how you could implement a system like this. However, in a live environment with many items, it would be better to only initialise/compile the nested/serialized array for a specific item as needed. Caching is your friend here.
Alternatively you can take this same approach and implement it in such a way that it only needs to query the datasource to reduce the memory footprint.
There are two questions actually.
How to build the character hierarchy with design pattern
How to rule the market with validation
This is my modeling suggestion
To the first problems. There are different properties to define a Character. Race and Tier. I am not sure if Area is the location of the character position. Or it is the biome of the character. If latter, then the character properties become three. I suggest to model the hierarchy with composition over inheritance. Otherwise, using traditional inheritance would result in many combination of classes (default Orc, Basic Orc, Pro Orc etc). Not to mention if more Race or Tier to come in the future.
To the second problem, I would not recommend chain of responsibility. Because with CoR, you need to define a long chain. Each processing object handle a variation of Character. You will need many processing object similar to the first problem.
I suggest to have a data structure registered what kind of Character could trade how many items. In my suggestion this is the responsibility of MarketAuthority. Imagine it has a map-like structure inside, and each entry it has a record call MarketRegulation. MarketRegulation define what kind of Character can buy or sell how many items. When trade happens, pass the character to MarketAuthority and find which MarketRegulation match the character. From the MarketRegulation result you know how many the character can trade.
Below is the example of MarketAuthority implementation.
class MarketAuthority
{
private $regulations;
function __construct()
{
$this->regulations = array();
// setting up regulations. Ideally they should be come from data source
// orc
$orc = new Character(null, null, new Orc());
array_push($this->regulations, new MarketRegulation($orc, new ActualLimit(50), TradeType::BUY));
.
.
.
// basic orc
$basicOrc = new Character(new Basic(), null, new Orc());
array_push($this->regulations, new MarketRegulation($basicOrc, new ActualLimit(50), TradeType::BUY));
$proForestOrc = new Character(new Pro(), new Forest(), new Orc());
array_push($this->regulations, new MarketRegulation($proForestOrc, new ActualLimit(150), TradeType::BUY));
// more to be set ...
}
public function buyCheck(Character $character, int $quantity): bool
{
$regulation = $this->getRegulation($character, TradeType::BUY);
return $regulation->getLimit()->allow($character, $quantity);
}
public function sellCheck(Character $character, int $quantity): bool
{
$regulation = $this->getRegulation($character, TradeType::SELL);
return $regulation->getLimit()->allow($character, $quantity);
}
public function buy(Character $character, int $quantity)
{
if ($this->buyCheck($character, $quantity)) {
$arrow = $character->getArrow() + $quantity;
$character->setArrow($arrow);
}
}
public function sell(Character $character, int $quantity)
{
if ($this->sellCheck($character, $quantity)) {
$arrow = $character->getArrow();
$character->setArrow($arrow - $quantity);
}
}
public function getRegulation(Character $character, TradeType $tradeType): MarketRegulation
{
$regulation = array_filter($this->regulations, function (MarketRegulation $r) use ($character, $tradeType) {
return $r->getCharacterConfig()->equals($character) && $r->getTradeType() == $tradeType;
});
return array_pop($regulation);
}
}
For a runnable prototype, please visit https://github.com/stackoverflow-helpdesk/free-market. Below is the prototype console output, simulating how the MarketAuthority regulate trades.
Basic Desert Elf [0] can buy up to 150 arrow
Basic Desert Elf [0] buy arrow*150
Basic Desert Elf [150]
Basic Desert Elf [150] only sell up to 50%. i.e.75
Basic Desert Elf [150] sell arrow*80
Basic Desert Elf [150]
Basic Desert Elf [150] cannot sell arrow*80
Orc [0] can buy up to 50 arrow
Orc [0] buy arrow*80
Orc [0]
Orc [0] cannot buy arrow*80

Behat Scenario Outline Float values

Consider the following scenario for API testing
Given I am using the API Service
When I send the <request> as <method> to <endpoint> endpoint with <key> having value <value>
Then The response status code is 200
Examples:
| request | method | endpoint | key | value |
| "All Keys" | "POST" | "endpointName" | "numericField" | 15 |
| "All Keys" | "POST" | "endpointName" | "numericField" | 15.12345 |
The above example creates a request with the specified parameters.
My problem is that while the integer (15) value is passed to the function accordingly, the float (15.12345) is converted into a string ("15.12345"). This happens straight as the function is called; it is not modified later on during another step.
Is there a way to keep the float value from turning into a string?
As requested, the send request step method is:
$data = $this->fulfilmentOptions->getDataValue($request);
$uri = $this->getMinkParameter('base_url') . $this->setEndpoint($endpoint);
array_walk_recursive($data, function(&$item, $originalKey) use ($key, $value) {
if ($originalKey === $key) {
$item = $value;
}
});
try {
$this->response = $this->client->request($method, $uri, [
'headers' => $this->fulfilmentOptions->getDataValue('CreateOrder API Headers'),
'body' => json_encode($data)
]);
} catch (\GuzzleHttp\Exception\RequestException $e) {
$this->response = $e->getResponse();
}
$this->responseContent = $this->response->getBody()->getContents();
One way of fixing the issue is to use floatval method for 'value' key to make sure you get the right type.

Storing/updating multiple rows in from same method in Laravel

I have a table like this
month | rate | admin_id
0 | 1000 | 1
I am storing data with following method:
public function store(Request $request)
{
foreach ($request->admin_id as $key => $val){
$adminbillings = AdminBilling::create([
'month' => 0,
'rate' => $request->rate[$key],
'admin_id' => $val,
]);
}
return redirect('/');
}
When I add a new row in the table using this method, I also want to replace the month of previous row (which is 0) with the timestamp of the new line. So the table will become
month | rate | admin_id
29-08-2017 | 1000 | 1
0 | 2000 | 1
I can add multiple lines from single method, but here one is store and one is update, and by default update requires two arguments. But may be I am mistaken somewhere. Can you please guide me how to achieve my target?
I can access the previous row inside the method using this line:
$ended_at = AdminBilling::where('admin_id', $val)->where('month', 0)->get();
This is not best solution but you will achieve your target.
To update previous record after insertion , you can do like this -
$res = AdminBilling::select('id')->orderBy('id','desc')->skip(1)->take(1)->first(); //to get prev. row
AdminBilling::where('id',$res->id)->update(['month',date()]); // to update month
put these lines in your store function as -
public function store(Request $request)
{
foreach ($request->admin_id as $key => $val){
$adminbillings = AdminBilling::create([
'month' => 0,
'rate' => $request->rate[$key],
'admin_id' => $val,
]);
$res = AdminBilling::select('id')->orderBy('id','desc')->skip(1)->take(1)->first();
AdminBilling::where('id',$res->id)->update(['month',date()]);
}
return redirect('/');
}
Hope this will helpful for you.
When you use create method it returns the instance of your inserted row. So you can use $adminbillings->id if you need the access to previously insterted row.

PHP custom sort preg_match_all?

So I know there are articles out there about custom ordering but I think my situation may be a little different (Or could just be my workaround sucks :P ). Here is the quick scenario.
I pull XML data from curl off a report url that we have. (not properly formatted!) So let's use the following example: I get the following data back in one long list.
<Site>My Sites</Site>
<Customer>PHPSTEVE</Customer>
<Name>Griswald</Name>
<Site> Evil Dead</Site>
<Customer>Bruce</Customer>
<Name>Campbell</Name>
I can't use the XML function because the format header is incorrect so I won't get into that.. So instead I use a preg_match_all
preg_match_all("/<Site>(.*?)<\/Site>|<Customer>(.*?)<\/Customer>|<Name>(.*?)<\/Name>/is", $resp, $output_array[]);
Then I go through and I know this is not good but it works..
foreach(array_reverse($output_array[0][0]) as $info){
$myinfo = $info . $myinfo;
}
Everything works as I use a str_replace and just put in my table tags etc.. Obviously I'm doing the reading in reverse.. it's ok I want it that way for this.
Site | Customer | Name
----------+----------+------
Evil Dead | Bruce | Campbell
What if I want "Customer" field first? So basically I'm changing the horizontal order of Site and Customer etc... Remember there are multiple records all in a line so I am trying to figure out how I would move Customer to where I want it.. or any field for that matter. Based off of my code above, I don't believe I can accomplish it the way I want to so I'm not sure of another way to do it.
I would like to report it as this:
Customer | Site | Name
---------+-----------+------
Bruce | Evil Dead | Campbell
Any help would be appreciated. NOTE: I don't care about reading bottom up or top down.. just field changes. Thanks
UPDATE: The XMl issue I run into is:
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<results>
<Site>My Sites</Site>
<Customer>PHPSTEVE</Customer>
<Name>Griswald</Name>
<Site> Evil Dead</Site>
<Customer>Bruce</Customer>
<Name>Campbell</Name>
</results>
Maybe something like this?
function sortCustomerKeys($a, $b)
{
if ( $a == 'Customer' && $b != 'Customer' ) {
return 1;
} else if ( $a != 'Customer' && $b == 'Customer' ) {
return -1;
}
return strnatcasecmp($a, $b);
}
$customers = array();
$advance = array(
'Customer' => 0,
'Name' => 0,
'Site' => 0
);
if ( preg_match_all('#<(Customer|Name|Site)>\\s*(.*?)\\s*</\\1>#is', $resp, $m) ) {
for ($i = 0, $j = 0; $i < count($m[1]); $i++) {
$tag = $m[1][$i];
$value = $m[2][$i];
$customers[$j][$tag] = $value;
$advance[$tag] = 1;
if ( array_sum($advance) == count($advance) ) {
$advance = array(
'Customer' => 0,
'Name' => 0,
'Site' => 0
);
uksort($customers[$j], 'sortCustomerKeys');
$j++;
}
}
}
var_dump($customers);

Comparing two very similar php classes (values of their attributes)

I have two very similar classes. Lets say Class A and Class B.
+---------------+ +---------------+
| Class A | | Class B |
|---------------| |---------------|
| Name | | Name |
| ZIP | | ZIP |
| TelPhone | | TelPhone |
| | | MobilePhone |
+---------------+ +---------------+
I want to compare them in the values for all common attributes.
Here's a way I tried it, but doing it for all attributes (got more than only 3 attributes) looks like a overkill for me:
$differences = array();
if($classA->getName() != $classB->getName()) {
array_push(
$differences,
array('name' => array(
'classA' => $classA->getName(),
'classB' => $classB->getName()
)
));
}
// and the same code for every attribute....
What's the best approach here?
Additional to the handiwork, it is also not automatically updated if the classes are getting altered. For example if Class A gets also a MobilePhone attribute.
Please don't tell me, that I should do some Polymorphism, it's just an example to clarify.
I'm interested in the difference, so not only the attributes, also the values itself inside the attributes.
thanks
It's not the sexiest thing (because of the substring to getters.., but I can't go with properties, I think sym, but I got it:
// getting an array with all getter methods of a class
private function getGettersOf($object) {
$methodArray = array();
$reflection = new ReflectionClass($object);
$methods = $reflection->getMethods();
foreach ($methods as $method) {
// nur getter
if(strtolower(substr($method->getName(), 0, 3)) == "get") {
array_push($methodArray, $method->getName());
}
}
return $methodArray;
}
// getting an array with all differences
public function getDifferences($classA, $classB) {
// get list of attributes (getter methods) in commond
$classAMethods = $this->getGettersOf($classA);
$classBMethods = $this->getGettersOf($classB);
$intersection = array_intersect($classAMethods, $classBMethods);
$differences = array();
// iterate over all methods and check for the values of the reflected methods
foreach($intersection as $method) {
$refMethodClassB = new ReflectionMethod($classB, $method);
$refMethodClassA = new ReflectionMethod($classA, $method);
if($refMethodClassB->invoke($classB) != $refMethodClassA->invoke($classA)) {
array_push(
$differences,
array($method => array(
'classA' => $refMethodClassA->invoke($classA),
'classB' => $refMethodClassB->invoke($classB)
)
));
}
}
return $differences;
}
Special thanks to George Marques's comment.

Categories