Easier way to unpack data of a .blend file with php? - php

Currently I want to read some data (metadata, scene names, mesh count, vertices count ...) from a .blend file with the unpack() function of PHP refering to the Blender SDNA documentation:
http://www.atmind.nl/blender/blender-sdna-256.html
Is there some easy solution to read all these information with some existing classes or libraries or do I have to read block by block from the file and write my own functions / clas / library (so I can create something like an object)?

After consultation with php manual I can tell you that php just doesn't provide way to read binary files, but I think there's quite nice way to do this (inspirited by c and fread)
class BinaryReader {
const FLOAT_SIZE = 4;
protected $fp = null; // file pointer
...
public function readFloat() {
$data = fread( $fp, self::FLOAT_SIZE);
$array = unpack( 'f', $data);
return $array[0];
}
// Reading unsigned short int
public function readUint16( $endian = null){
if( $endian === null){
$endian = $this->getDefaultEndian();
}
// Assuming _fread handles EOF and similar things
$data = $this->_fread( 2);
$array = unapack( ($endian == BIG_ENDIAN ? 'n' : 'v'), $data);
return $array[0];
}
// ... All other binary type functions
// You may also write it more general:
public function readByReference( &$variable){
switch( get_type( $variable)){
case 'double':
return $this->readDouble();
...
}
}
}
If you have any improvements or tips, just post them in the comment I'll be glad to extend the answer.

Related

Creating a translation class with PHP associative array

I have written a very simple translation class that is supposed to return the meaning associated with the phrase that I give to it. Under the hood, it loads translations from a csv upon construction into an associative array. Upon translation request, it checks the array. If the phrase is there as a key in the array, returns its value, which is its translation. If the phrase does not exist as a key, it loads the array from the file again (as there might be new translations), checks for the key again. If it does not find the key again, the phrase will be returned as is.
<?php
class Translate{
function __construct() {
$this->loadTranslations();
}
public function get($message, $lang = "de"): string{
if(key_exists($message, self::$de)){
return self::$de[$message];
}
else {
//Load translations again
$this->loadTranslations();
if(isset(self::$de[$message])){
return self::$de[$message];
}
else {
return $message;
}
}
}
protected static $de = [];
protected function loadTranslations() {
$file = fopen(__DIR__ . "/../data/de.csv", "r");
if($file){
while($line = fgets($file)){
$en_de = explode(":", $line);
self::$de[array_shift($en_de)] = array_shift($en_de);
}
}
fclose($file);
}
}
$t = new Translate();
echo $t->get("Hello") . PHP_EOL;
Content of de.csv is like this:
"Hi": "Hallo"
"Hello": "Hallo"
The problem is when asked for a translation, the class always returns the given phrase. When I dump the array, the phrase is there as a key, but there is no success in accessing $array[$phrase] as PHP does not find the key in the array!
The problem is that in your CSV file, you have quotes round the text, so although Hello exists, it's actually stored in the translation array as "Hello" so will not match.
You could either redo your translation file to not have the quotes, or you could use the functionality of fgetcsv() to read it and strip out any surrounding quotes (use : as the separator)...
protected function loadTranslations() {
$file = fopen(__DIR__ . "/a.csv", "r");
if($file){
while([$key, $trans] = fgetcsv($file, null, ":", '"')){
self::$de[$key] = $trans;
}
}
fclose($file);
}
Just looking at the code to fetch the translation, you could shorten it. First check that the translations are loaded, then return the translation - using ?? to say if it's not found, then return the original message...
public function get($message, $lang = "de"): string{
if(!isset(self::$de)){
$this->loadTranslations();
}
return self::$de[$message] ?? $message;
}
Your csv looks more like json to me.
I'd probably adjust the file to be json permanently, but until then, just convert it into a json string manually, then decode it to create your key-value pairs.
self::$de = json_decode(
'{' . implode(',', file(__DIR__ . "/a.csv")) . '}',
true
);
In other words, make all of your language files valid json. This way you can instantly cal json_decode() on the entire file contents and the array is ready. Keeping your file in the current format means individually isolating each line of text in the file and calling a function to parse it -- this is waaaaay too much work to be done each time.
Please consistently write your class variables at the top of your class.
$de should not be a variable name -- I am assuming it is referring to a specific language. $lang() should be used to specify the user's desired language and search for the appropriate filename.
Edit:
I really can't overstate how beneficial it is to convert your files to valid json -- it just makes everything cleaner. Here is a re-write of your code. I don't agree with the use of a static class variable, nor the constructor that that loads a language without know what is going to be used. And as previously mentioned there should be no variable that refers to a specific language ($de). The class variable $translations should be an associative array containing subarrays so that you can permanently load and access multiple translations at the same time.
Untested suggestion:
class Translate{
protected $translations = [];
protected function loadTranslations($lang)
{
$filePath = __DIR__ . '/' . $lang . '.json';
if (file_exists($filePath)) {
$this->translations[$lang] = json_decode(file_get_contents($filePath), true);
}
}
public function get($message, $lang = "de"): string
{
if (!isset($this->translations[$lang])) {
$this->loadTranslations($lang);
}
return $this->translations[$lang][$message] ?? $message;
}
// e.g. $newTrans = ['Good Day' => 'Guten Tag', ...]
public function set($lang, $newTrans)
{
if (!isset($this->translations[$lang])) {
$this->loadTranslations($lang);
}
$this->translations[$lang] += $newTrans; // insert or overwrite key-value pair(s)
file_put_contents(__DIR__ . '/' . $lang . '.json', json_encode($this->translations[$lang])); // commit to file
}
}
$t = new Translate();
echo $t->get("Hello") . PHP_EOL;

Can We Restrict PHP Variables to accept only certain type of values

i am wondering is it possible to restrict php variable to accept only certain type of variable it is defined to accept.
e-g if we see in C#
public int variable = 20;
public string variable2 = "Hello World";
so what would it be like in php if i want to restrict type for variable.
public int $variable = 20;
so that if i try to assign string to integer variable i get the error.
public int $varaible = "Hello World"; //As type is integer, it should throw error.
is there such thing defining types in PHP for variables before assigning values to it??
TL;DR Not directly, no. PHP is not strictly-typed. There are, however, a few workarounds that may work for you in the context of function parameters or properties of classes.
Long answer: PHP is not a strictly-typed language, but loosely-typed. You can give a variable any value you want, regardless of how it was initialized. So, you can't simply type something like int $myVar and expect $myVar = "foo"; to throw an error. But PHP does offer a few handy features to get you to the same end when dealing with function parameters or properties of a class.
Option 1: Type hints
You can use a "type hint" for function parameters:
class SomeClass
{
/* code here */
}
function foo(SomeClass $data)
{
/* code here */
}
foo() will only accept parameters of type SomeClass. Passing it, say, an int will throw a fatal error. This doesn't work in PHP < 7 if the parameters are intended to be base types, like int, string, etc., so you can't do function foo(int $data) {...}. That said, there are a few libraries out there that attempt to force it to work at the expense of a little speed. Also, PHP 7 adds a lot of support for this kind of thing, as does the Hack language based on PHP.
Pros:
Easy
Intuitive
Cons:
Only works for program-defined classes
Unavailable for base types
Option 2: Getters and Setters
You can also use getters and setters, like so:
class SomeClass
{
private $foo = 0;
function setFoo($val = 0)
{
// force it to be an int
if (is_integer($val) {
$this->foo = $val;
} else {
// throw an error, raise an exception, or otherwise respond
}
}
}
Pros:
Relatively easy
Relatively intuitive
Cons:
Only works in program-defined classes
Unavailable for base types
Requires lots of code
Option 3: Magic Methods
This method is my favorite, but also the most complicated. Use the __set() magic method to deal with class properties.
class MyClass {
private $type = 0; // we will force this to be an int
private $string = ''; // we will force this to be a string
private $arr = array(); // we will force this to be an array
private $percent = 0; // we will force this to be a float in the range 0..100
function __set($name, $value) {
switch ($name) {
case "type":
$valid = is_integer($value);
break;
case "string":
$valid = is_string($value);
break;
case "arr":
$valid = is_array($value);
break;
case "percent":
$valid = is_float($value) && $value >= 0 && $value <= 100;
break;
default:
$valid = true; // allow all other attempts to set values (or make this false to deny them)
}
if ($valid) {
$this->{$name} = $value;
// just for demonstration
echo "pass: Set \$this->$name = ";
var_dump($value);
} else {
// throw an error, raise an exception, or otherwise respond
// just for demonstration
echo "FAIL: Cannot set \$this->$name = ";
var_dump($value);
}
}
}
$myObject = new MyClass();
$myObject->type = 1; // okay
$myObject->type = "123"; // fail
$myObject->string = 1; // fail
$myObject->string = "123"; // okay
$myObject->arr = 1; // fail
$myObject->arr = "123"; // fail
$myObject->arr = array("123"); // okay
$myObject->percent = 25.6; // okay
$myObject->percent = "123"; // fail
$myObject->percent = array("123"); // fail
$myObject->percent = 123456; // fail
Pros:
Relatively easy
Intuitive
Extremely powerful: one setter to rule them all
Cons:
Only works in program-defined classes
Unavailable for base types
Requires lots of switching or if/else logic
Can cause problems with IDEs not auto-completing property types correctly
Here's a demo of this approach.
Closing Thoughts
Finally, if you're using an IDE like PHPStorm, don't forget about PHPDoc type hints:
/* #var integer */
$foo = 0; // will result in warnings if the IDE is configured properly and you try to do something like substr($foo, 1, 4);
And if you really want to go hard core, you can do strong typing using Hack, at the expense of making your code less portable and less compatible (for now) with major IDEs.
Of course, none of these is a substitute for explicitly validating user input and thoroughly testing the application's response to unexpected input types.
No. PHP is not a strictly typed language. You can however use type hints in functions and methods.
If class or interface is specified as type hint then all its children or implementations are allowed too.
Type hints can not be used with scalar types such as int or string. Resources and Traits are not allowed either.
The Scalar types being:
string
bool
int
float
Examples:
function (array $theArr) {
// body
}
class X {
public function __construct(SomeOtherClass $arg) {
// body
}
public function setFoo(Foo $foo) {
}
}
See the manual for more specifics: http://php.net/manual/en/language.oop5.typehinting.php
You have to made it by your own hands, example :
function setInt(&$var, $value) {
if(!is_integer($value) {
throw new Exception("Integer wanted " . gettype($value) . " received");
}
$var = $value;
}

Php - Copy Stream to itself

I'd like to perform an operation on the php://input stream but also return it.
For example, what I'd like to achieve is that:
php://input ==> OPERATION ==> php://input
Is that possible to make something like that ?
$input = fopen("php://input", "r");
$output = fopen("php://input", "w");
while (($buffer.= fgets($input, 1024)) !== false) {
// Do something with that buffer
// ??
fwrite($output, $buffer);
}
fclose($output);
fclose($input);
If you are fine with one of the operations supported as a filter in php, you can use the php://filter fopen wrapper.
Let's say you want to base64 decode the data for example:
$data = file_get_contents('php://filter/read=convert.base64-decode/resource=php://input');
Or:
$input = fopen('php://filter/read=convert.base64-decode/resource=php://input');
// now you can pass $input to somewhere and every read operation will
// return base64 decoded data ...
However, the set of operations supported as a filter in PHP is quite limited. If it does not fit your needs I would suggest to wrap the file pointer, in a class maybe. Here comes a very basic example, you might add buffering, caching or whatever...
class Input {
public static function read() {
return $this->process(file_get_contents('php://stdin'));
}
public function process($data) {
return do_whatever_with($data);
}
}
Then in the application code use:
$input = Input::read();

PHP Counter Using OOP

I'm new to OOP terminology, I am trying to create a class that make a hit counter.
I try the code below but it create just a counter.txt page with inside value 1. I dont know why its not incrementing.
class LOGFILE {
public function READ($FileName) {
$handle = fopen($FileName, 'r');
$fread = file_get_contents($FileName);
return $fread;
fclose($handle);
}
public function WRITE($FileName, $FileData) {
$handle = fopen($FileName, 'w');
$FileData = $fread +1;
fwrite($handle, $FileData);
fclose($handle);
}
}
$logfile = new LOGFILE();
$logfile -> WRITE("counter.txt",$FileData);
echo $logfile -> READ("counter.txt");
The reason is that $fread is local variable for both READ and WRITE methods. You need to make it private global variable for your class:
class LOGFILE {
private $fread;
public function READ($FileName) {
$this->fread = file_get_contents($FileName);
return $this->fread;
}
public function WRITE($FileName) {
$this->READ($FileName);
$handle = fopen($FileName, 'w');
$FileData = $this->fread +1;
fwrite($handle, $FileData);
fclose($handle);
}
}
$logfile = new LOGFILE();
$logfile -> WRITE("counter.txt");
echo $logfile -> READ("counter.txt");
Note: I have removed fopen and fclose because file_get_contents does not need it. In write you can use file_put_contents. Removed not used variable $FileData too. It's always a good practice to create variables methods and classes when they are needed.
Also take a look at best practices how to name your classes, variables, methods and so on. Here's best guide, IMO.
Let's start going over the corrected code and see what was missing:
<?php
class LOGFILE {
public function READ($FileName) {
$handle = fopen($FileName, 'r');
$fread = fgets($handle, 8192);
fclose($handle);
return $fread;
}
public function WRITE($FileName, $FileData) {
$counter = $this->READ($FileName);
$handle = fopen($FileName, 'w');
fwrite($handle, $FileData + $counter);
fclose($handle);
}
}
$logfile = new LOGFILE();
$FileData = 1;
$logfile -> WRITE("counter.txt",$FileData);
echo $logfile -> READ("counter.txt")."\n";
$logfile -> WRITE("counter.txt",$FileData);
echo $logfile -> READ("counter.txt")."\n";
?>
use of fgets instead of file_get_contents in READ (you can choose to use file_get_contents but I rather stay consistent with the other function that uses fopen)
use of READ inside function WRITE (the principal of code-reuse)
open of file with write permissions in WRITE: 'w'
init $FileData = 1;
no need to hold a private member: $fread
most important: do not write statements after return (like you did in READ) - statements that are written after return will not be executed!
This solution was tested successfully.
OOP must be used where it's needed. You need a simple thing so, no need of OOP.
<?php
function addValue($file='counter.txt', $amount=1) {
if( false == is_file($file) ) {
return false;
}
$initial = file_get_contents($file);
return #file_put_contents($initial+$amount);
}
addValue();
?>
Test your OOP knowledge on something complex, like a shopping cart or some other concept.
EDIT // so, if you need a simple example that looks complex, here you go :)
<?php
class log {
public $file = '';
private $amount = 0;
public function __construct( $file ) {
$this->file = $file;
$this->amount = 1;
}
public function makeAdd() {
$initial = file_get_contents($this->file);
return #file_put_contents($this->file, $initial + $this->amount);
}
function __call($f, $args) {
switch( $f ) {
case 'add':
if(isset($args[0]) && !empty($args[0])) {
$this->amount = (int)$args[0];
}
if( $this->amount == 0 ) {
throw new Exception('Not a valid amount.');
}
return $this->makeAdd();
break;
}
}
}
try {
// create log
$L = new log('count.txt');
// this will add 2
var_dump($L->add(2));
// this will also add 2
var_dump($L->add());
// until you rewrite the amount
var_dump($L->add(1));
// final result -> 5
} catch(Exception $e) {
die($e->getMessage());
}
?>
Good luck!
Use UpperCamelCase for class names. LogFile, not LOGFILE. When you have a variable and the most interesting thing about it is that it's expected to hold a reference to something that is_a LogFile you should name it logFile.
Use lowerCamelCase for functions. read and write, not READ and WRITE
No spaces around the arrow operator
Code after a return statement in a method can never be reached, so delete it.
read() does not use the handle returned by fopen, so don't call fopen
the temp variable $freed doesn't help us understand the code, so we can lose it
read is a slightly unconventional name. If we rename the function to getCount it will be more obvious what it does.
You said you wanted to make a hit counter. So rename the class from LogFile to HitCounter, and the variable to hitCounter
the $FileData parameter to write doesn't get used because the variable is re-assigned inside the function. We can lose it.
The write method is supposed to add one to the number in the file. Write doesn't really express that. Rename it to increment.
Use a blank line between functions. The procedural code at the end should generally be in a separate file, but here we can just add a couple of extra lines. Delete the blanks between the last three lines of code.
Don't repeat yourself - we shouldn't have to mention 'counter.txt' more than once. OOP is all about combining data structures and behaviour into classes, so make a class private variable to hold the filename, and pass it via a constructor
$fread doesn't exist in the scope of increment, so we can't use it. This won't work. Replace it with a call to to getCount()
Swap the first two lines of increment, so we're not doing two concurent accesses to the same file, although we might be running inside a server that's running our script twice and still doing two concurrent accesses.
Rename the variable $FileData to $count, since that's what it is.
Replace the fopen,fwrite,fclose sequence with file_put_contents, since that does the same thing and is more succinct.
We need tag, since our php code continues to the end of the file.
That leaves us with:
<?php
class HitCounter {
private $fileName;
public function __construct($fileName){
$this->fileName = $fileName;
}
public function getCount() {
return file_get_contents($this->fileName);
}
public function increment() {
$count = $this->getCount() + 1;
file_put_contents($this->fileName, $count);
}
}
$hitCounter = new HitCounter("counter.txt");
$hitCounter->increment();
echo $hitCounter->getCount();
You can create a static counter and increment it each time (instead of create file)
<?php
class CountClass {
public static $counter = 0;
function __construct() {
self::$counter++;
}
}
new CountClass();
new CountClass();
echo CountClass::$counter;
?>

PHP HTTP Raw I/O Tracking on Apache

I'm trying to find a way to measure bytes transferred in or out of a web application built on php+apache. One problem is that all I/O is done by a native PHP extension, which is passed a handle to one of the built-in streams: php://input or php://output.
I have examined the following alternatives:
1.) ftell on stream wrapper
After encountering this question, my first intuition was to try using ftell on the stream wrapper handle after the I/O operation; roughly:
$hOutput = fopen('php://output', 'wb');
extensionDoOutput($hOutput);
$iBytesTransferred = ftell($hOutput);
This seems to work for the input wrapper, but not the output (which always returns zero from ftell).
2.) Attach stream filter
A non-modifying stream filter would seem like a reasonable way to count bytes passing through. However, the documentation seems a bit lacking and I haven't found a way to get at lengths without doing the iterate+copy pattern as in the example:
class test_filter extends php_user_filter {
public static $iTotalBytes = 0;
function filter(&$in, &$out, &$consumed, $closing) {
while ($bucket = stream_bucket_make_writeable($in)) {
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
test_filter::$iTotalBytes += $consumed;
return PSFS_PASS_ON;
}
}
stream_filter_register("test", "test_filter")
or die("Failed to register filter");
$f = fopen("php://output", "wb");
stream_filter_append($f, "test");
// do i/o
Unfortunately this seems to impose a significant reduction in throughput (>50%) as the data is copied in and out of the extension.
3.) Implement stream wrapper
A custom stream wrapper could be used to wrap the other stream and accumulate bytes read/written:
class wrapper {
var $position;
var $handle;
function stream_open($path, $mode, $options, &$opened_path)
{
$this->position = 0;
...
$this->handle = fopen($opened_path, $mode);
return $this->handle != false;
}
function stream_read($count)
{
$ret = fread($this->handle, $count);
$this->position += strlen($ret);
return $ret;
}
function stream_write($data)
{
$written = fwrite($this->handle, $data);
$this->position += $written;
return $written;
}
function stream_tell()
{
return $this->position;
}
function stream_eof()
{
return feof($this->handle);
}
...
}
stream_wrapper_register("test", "wrapper")
or die("Failed to register protocol");
$hOutput = fopen('test://output', 'wb');
extensionDoOutput($hOutput);
$iBytesTransferred = ftell($hOutput);
Again, this imposes a reduction in throughput (~20% on output, greater on input)
4.) Output buffering with callback
A callback can be provided with ob_start to be called as chunks of output are flushed.
$totalBytes = 0;
function cb($strBuffer) {
global $totalBytes;
$totalBytes += strlen($strBuffer);
return $strBuffer;
}
$f = fopen("php://output", "wb");
ob_start('cb', 16384);
// do output...
fclose($f);
ob_end_flush();
Again, this works but imposes a certain throughput performance penalty (~25%) due to buffering.
Option #1 was forgone because it does not appear to work for output. Of the remaining three, all work functionally but affect throughput negatively due to buffer/copy mechanisms.
Is there something instrinsic to PHP (or the apache server extensions) that I can use to do this gracefully, or will I need to bite the bullet on performance? I welcome any ideas on how this might be accomplished.
(note: if possible I am interested in a PHP application-level solution... not an apache module)
I would stick to the output buffer callback you can just return FALSE to pass through:
class OutputMetricBuffer
{
private $length;
public function __construct()
{
ob_start(array($this, 'callback'));
}
public function callback($str)
{
$this->length += strlen($str);
return FALSE;
}
public function getLength()
{
ob_flush();
return $this->length;
}
}
Usage:
$metric = new OutputMetricBuffer;
# ... output ...
$length = $metric->getLength();
The reasons to use the output buffer callback is because it's more lightweight than a filter which needs to consume all buckets and copy them over. So it's more work.
I implemented the callback inside a class so it has it's own private length variable to count up with.
You can just create a global function as well and use a global variable, however another tweak might be to access it via $GLOBALS instead of the global keyword so PHP does not need to import the global variable into the local symbol table and back. But I'm not really sure if it makes a difference, just another point which could play a role.
Anyway I don't know as well if returning FALSE instead of $str will make it faster, just give it a try.
As bizarre as this is, using the STDOUT constant instead of the result of fopen('php://output') makes ftell() work correctly.
$stream = fopen('php://output','w');
fwrite($stream, "This is some data\n");
fwrite($stream, ftell($stream));
// Output:
// This is some data
// 0
However:
fwrite(STDOUT, "This is some data\n");
fwrite(STDOUT, ftell(STDOUT));
// Output:
// This is some data
// 17
Tested PHP/5.2.17 (win32)
EDIT actually, is that working correctly, or should it be 18? I never use ftell() so I'm not 100% sure either way...
ANOTHER EDIT
See whether this suits you:
$bytesOutput = 0;
function output_counter ($str) {
$GLOBALS['bytesOutput'] += strlen($str);
return $str;
}
ob_start('output_counter');
$stream = fopen('php://output','w');
fwrite($stream, "This is some data\n");
var_dump($bytesOutput);

Categories