I have a simple ZF2 application that uses two tables and a service and I'm trying to convert it to run on ZF3. I can't work out how to update the service manager code. Here's an example of one of the controllers
<?php
namespace LibraryRest\Controller;
use Zend\Mvc\Controller;
use Library\Service\SpydusServiceInterface;
use Library\Service\SpydusService;
use Library\Model\BookTitle;
use Library\Model\BookTitleTable;
use Library\Model\Author;
use Library\Model\AuthorTable;
use Zend\Mvc\Controller\AbstractRestfulController;
use Zend\View\Model\JsonModel;
class SearchRestController extends AbstractRestfulController {
protected $bookTitleTable;
public function getBookTitleTable() {
if (! $this->bookTitleTable) {
$this->bookTitleTable = $this->getServiceLocator ()->get ( 'BookTitleTable' );
}
return $this->bookTitleTable;
}
protected $authorTable;
public function getAuthorTable() {
if (! $this->authorTable) {
$sm = $this->getServiceLocator ();
$this->authorTable = $sm->get ( 'AuthorTable' );
}
return $this->authorTable;
}
protected $spydusService;
public function __construct(SpydusServiceInterface $spydusService) {
$this->spydusService = $spydusService;
}
public function getList($action, $first, $last) {
if ($action == 'search') {
return $this->searchAction ( $first, $last );
}
}
public function searchAction() {
$message = array ();
$results = array ();
$first = urldecode ( $this->params ()->fromRoute ( 'first' ) );
$last = urldecode ( $this->params ()->fromRoute ( 'last' ) );
if ($this->getAuthorTable ()->getAuthorId ( $first, $last ) === false) {
$results [0] = array ('count' => 0, 'message' => 'This author not found in database', 'first' => $first, 'last' => $last, 'title' => '', 'titleCode' => '', 'link' => '', 'authorId' => '' );
} else {
$authorId = $this->getAuthorTable ()->getAuthorId ( $first, $last )->author_id;
$books = $this->spydusService->searchBooks ( $first, $last );
$count = count ( $books );
foreach ( $books as $foundTitle ) {
if ($foundTitle->getMessage () == 'Nothing found') {
$results [0] = array ('count' => 0, 'message' => 'Nothing found by library search', 'first' => $first, 'last' => $last, 'title' => '', 'titleCode' => '', 'link' => '', 'authorId' => '' );
break;
}
$searchBooks = $this->getBookTitleTable ()->getId ( $foundTitle->getTitle (), $authorId );
if ($searchBooks->count () == 0) {
$addUrl = "http://newlib.rvw.dyndns.org/library/search/add/" . $authorId . '/' . $foundTitle->getTitle ();
$results [] = array ('count' => $count, 'message' => $foundTitle->getMessage (), 'title' => $foundTitle->getTitle (), 'titleCoded' => $foundTitle->getTitleCoded (), 'first' => $foundTitle->getAuthorFirst (), 'last' => $foundTitle->getAuthorLast (), 'link' => $foundTitle->getLink (), 'authorId' => $authorId, 'addUrl' => $addUrl );
}
}
}
if (count ( $results ) == 0) {
$results [0] = array ('count' => 0, 'message' => 'Nothing found by library search', 'first' => $first, 'last' => $last, 'title' => '', 'titleCode' => '', 'link' => '', 'authorId' => '' );
}
return new JsonModel ( $results );
}
}
What code should I use instead of the getServiceLocator() call as this is no longer supported in ZF3? Elsewhere on Stack Overflow someone had replied to another question and suggested using a createService function but this has been dropped from ZF3 as well.
There are a couple of different approaches, but you're already using the most common one: passing the dependencies in through the constructor. You're currently doing this for your $spydusService class, so change the constructor to also accept arguments for the two table clases, something like:
class SearchRestController extends AbstractRestfulController
{
protected $bookTitleTable;
protected $authorTable;
protected $spydusService;
public function __construct(SpydusServiceInterface $spydusService, $bookTitleTable, $authorTable)
{
$this->spydusService = $spydusService;
$this->bookTitleTable = $bookTitleTable;
$this->authorTable = $authorTable;
}
[etc.]
}
then, somewhere you already have a factory for the SearchRestController (it might be a closure in your Module.php class, or a standalone factory class). You'll want to modify this to pass in the extra arguments:
public function getControllerConfig()
{
return array(
'factories' => array(
'SearchRestController' => function ($sm) {
$spydusService = $sm->get('SpydusService'); // this part you already have
$bookTitleTable = $sm->get('BookTitleTable');
$authorTable = $sm->get('AuthorTable');
return new SearchRestController($spydusService, $bookTitleTable, $authorTable);
}
)
);
}
You are going to want to create a factory to build controller. This new class will implement FactoryInterface. In the __invoke function, you'll use the $container instance to retrieve all of your dependencies of your controller and either pass them as arguments in the constructor or set them on the constructor instance. Then just return your controller instance from that function.
Your controller will need to be updated with fields to support this. You will also need to be sure to register your factory in your configuration.
Related
I have a system where I am creating multiple classes that all extend from an abstract class.
Each class also declares 'settings' for that particular class type.
Example:
class First extends Base {
protected $name = 'First';
protected $lug = 'first';
protected $fields = [
'name',
'address',
'phone',
];
function __construct()
{
parent::__construct();
}
public function abstractMethod()
{
// do stuff for this particular class
}
}
and
class Second extends Base {
protected $name = 'Second';
protected $lug = 'second-one';
protected $fields = [
'first-name',
'last-name',
'email',
];
function __construct()
{
parent::__construct();
}
public function abstractMethod()
{
// do stuff for this particular class
}
}
Now what I want to be able to do is grab all extended classes and their 'settings' and return something like this:
$classes = [
'first' => [
'name' => 'First',
'slug' => 'first',
'fields' => ['name', 'address', 'phone']
],
'second' => [
'name' => 'Second',
'slug' => 'second-one',
'fields' => ['first-name', 'last-name', 'email']
]
];
So how would I go about doing this? Is there a better way?
I am using Laravel if that helps.
Edit: To Explain why not a duplicate
I'm not just after a way to get classes and their information I am after a way to architect this situation. I am essentially creating an extensible plugin system and need a way to Tell-Don't-Ask which plugins have been added.
I didn't try it, but it should work. Or it'll directs you.
$result = array();
foreach (get_declared_classes() as $class) {
if (is_subclass_of($class, 'Base'))
$result[] = get_class_vars($class);
}
But your properties needs to be public also.
What about using ReflectionClass? Getting properties is quite easy, example from manual below. Listing extended classes should be easy too.
<?php
class Bar {
protected $inheritedProperty = 'inheritedDefault';
}
class Foo extends Bar {
public $property = 'propertyDefault';
private $privateProperty = 'privatePropertyDefault';
public static $staticProperty = 'staticProperty';
public $defaultlessProperty;
}
$reflectionClass = new ReflectionClass('Foo');
var_dump($reflectionClass->getDefaultProperties());
Output:
array(5) {
["staticProperty"]=>
string(14) "staticProperty"
["property"]=>
string(15) "propertyDefault"
["privateProperty"]=>
string(22) "privatePropertyDefault"
["defaultlessProperty"]=>
NULL
["inheritedProperty"]=>
string(16) "inheritedDefault"
}
Using ReflectionObject you can do it like this:
$result = array();
foreach (get_declared_classes() as $class) {
if (is_subclass_of($class, 'Base')) {
$obj = new $class;
$refObj = new ReflectionObject($obj);
$props = $refObj->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED);
$classProps = array();
foreach ($props as $prop) {
$property = $refObj->getProperty($prop->getName());
$property->setAccessible(true);
$classProps[$prop->getName()] = $property->getValue($obj);
}
$result[$class] = $classProps;
}
}
print_r($result);
Output:
Array (
[First] => Array (
[name] => First
[lug] => first
[fields] => Array (
[0] => name
[1] => address
[2] => phone
)
)
[Second] => Array (
[name] => Second
[lug] => second-one
[fields] => Array (
[0] => first-name
[1] => last-name
[2] => email
)
)
)
I have this method:
private function formatCliendCardData($data) {
$formatedData = array();
$formatedData['first_name'] = trim($data['name']);
$formatedData['last_name'] = trim($data['surname']);
$formatedData['date_of_birth'] = $data['birth_year'] . '-' . sprintf("%02d", $data['birth_month_id']) . '-' . sprintf("%02d", $data['birth_day']); //[yyyy-mm-dd]
$formatedData['sex'] = ($data['sex_id'] == 's1'? 'm' : 'f');
$formatedData['county'] = 'Latvija'; //#TODO get real data
$formatedData['post_index'] = (int) preg_replace( '/[^0-9]/', '', $data['post_index']);
$formatedData['city'] = trim($data['city']);
$formatedData['street'] = trim($data['street']);
$formatedData['house_nr'] = trim($data['house_nr']);
$formatedData['phone'] = trim($data['phone']);
$formatedData['email'] = trim($data['email']);
return $formatedData;
}
I run in this problem from time to time. I have big method that just reformats data and returns it. It looks ugly. There is few other aproaches Im aware -- just make foreach loop, but there are exeptions, so i need make 'ifs' anyway. What is better way to reformat such data in your opinon?
I aggree with helloei,
create a class to handle all the formating and validation in the setter.
class Person
{
public function setName($name)
{
$this->name = trim($name);
}
...
}
and then create the object in your function:
private function formatCliendCardData($data) {
$person = new Person();
$person->setName($data['name`])
...
if you have highly custom condition to reformat some datas i think you don't have any other solution that apply this condition manually with some if/else or other custom loop.
if else, you have some unified condition to reformat you can aggregate this and apply in batch at your datas. In other word for my opinion the only other solution are: generalize conditions of your reformatting operations and apply this at your data.
this is Object Oriented approach
IMHO In those cases you have to reduce and clean your code. Often I use an helper function that allows me to split data and formatting rules.
// helper function (OUTSITE YOUR CLASS)
function waterfall($input=NULL,$fns=array()){
$input = array_reduce($fns, function($result, $fn) {
return $fn($result);
}, $input);
return $input;
}
// your formatting rules
$rules = array(
'first_name' => array('trim'),
'last_name' => array('trim'),
'date_of_birth' => array(
'f0' => function($x){
return sprintf("%s-%02d-%02d",
$x['birth_year'],
$x['birth_month_id'],
$x['birth_day']
);
},
'trim'
),
'sex' => array(
'f0' => function($x){
if($x['sex_id'] == 's1'){
return 'm';
}else{
return 'f';
}
}
),
'post_index' => array(
'f0' => function($x){
return (int) preg_replace('/[^0-9]/', NULL, $x);
},
'trim'
),
'city' => array('trim'),
'email' => array('strtolower','trim')
);
// your data
$data = array(
'first_name' => ' Andrea',
'last_name' => 'Ganduglia ',
'date_of_birth' => array(
'birth_year' => '1899',
'birth_month_id' => 5,
'birth_day' => 7
),
'post_index' => 'IT12100',
'city' => 'Rome',
'email' => 'USER#DOMAIN.tld '
);
// put all together
$formatedData = array();
foreach($data as $k => $v){
$formatedData[$k] = waterfall($v,$rules[$k]);
}
// results
print_r($formatedData);
/*
Array
(
[first_name] => Andrea
[last_name] => Ganduglia
[date_of_birth] => 1899-05-07
[post_index] => 12100
[city] => Rome
[email] => user#domain.tld
)
*/
How do I have multiple custom errors messages with Respect Validation.
I have some input that I want to validate against multiple validators. And I want a custom error message for each validation.
This is what I tried:
try {
Respect\Validation\Validator::create()
->key('foo',
v::length(20)->setName('bar')->setTemplate('Custom length message.')
->alnum()->setName('baz')->setTemplate('Custom alnum message.')
)
->assert([
'foo' => 'Hello, world!',
]);
} catch (Respect\Validation\Exceptions\ValidationException $exception) {
$errors = $exception->findMessages([
'bar',
'baz',
]);
var_dump($errors);
}
The output is:
array (size=2)
'bar' => string '' (length=0)
'baz' => string 'Custom alnum message.' (length=21)
I expected it to output both custom error messages.
Idealy I could get an array of messages for 1 input like:
var_dump($exception->findMessages(['foo']));
Would give me:
array (size=1)
'foo' =>
array (size=2)
0 => string 'Custom length message.' (length=22)
1 => string 'Custom alnum message.' (length=21)
This question seems like tumble weed.
You can not chain them together and get the custom message, because the last custom message you call will simply be assigned to the ruleset as opposed to he individual rules due to the implementation of the chaining.
To demonstrated this, I cloned it from git, created a bin directory, and modified your sample slightly with this test.php
<?php
set_include_path(implode(PATH_SEPARATOR, array(
realpath('../library')
)));
function __autoload($class_name) {
include $class_name . '.php';
}
use Respect\Validation\Validator as v;
try {
$chained = Respect\Validation\Validator::create()
->key('foo',
v::length(20)->setName('bar')->setTemplate('Custom length message.')
->alnum()->setName('baz')->setTemplate('Custom alnum message.')
);
print_r($chained);
$chained->assert(array(
'foo' => 'Hello, world!',
));
} catch (Respect\Validation\Exceptions\ValidationException $exception) {
$errors = $exception->findMessages(array(
'bar',
'baz',
));
var_dump($errors);
}
the print_r($chained) shows us:
Respect\Validation\Validator Object
(
[rules:protected] => Array
(
[00000000791c0e000000000030f3f15e] => Respect\Validation\Rules\Key Object
(
[mandatory] => 1
[reference] => foo
[validator] => Respect\Validation\Validator Object
(
[rules:protected] => Array
(
[00000000791c0e030000000030f3f15e] => Respect\Validation\Rules\Length Object
(
[minValue] => 20
[maxValue] =>
[inclusive] => 1
[name:protected] =>
[template:protected] =>
)
[00000000791c0e020000000030f3f15e] => Respect\Validation\Rules\Alnum Object
(
[additionalChars] =>
[stringFormat] => /^(\s|[a-zA-Z0-9])*$/
[name:protected] =>
[template:protected] =>
)
)
[name:protected] => baz
[template:protected] => Custom alnum message.
)
[name:protected] => foo
[template:protected] =>
)
)
[name:protected] =>
[template:protected] =>
)
You may notice that the ruleset pick up the last name and well as the last template passed in, and that neither of the actual validation objects got the Name or the Template. I don't see any way in the library to actually do what you are attempting to do.
So I decided to make a way. In my ../bin directory I created this class, extending the Valditor class.
<?php
use Respect\Validation\Validator as v;
class BubbaValidator extends v {
public function getRuleset($rulename = null){
if (is_null($rulename)) return $this->rules;
foreach ($this->rules as $rule){
if ($rule->getName() == $rulename){
return $rule;
}
}
}
public function getValidatorRules($rulesetName, $ruleType=null){
$ruleset = $this->getRuleset($rulesetName);
$validators = $ruleset->validator;
if (is_null($ruleType)){
return $validators;
}
foreach ($validators->rules as $key=>$validator){
if (get_class($validator) === 'Respect\Validation\Rules\\'.$ruleType){
$validator->name = "bar";
$validator->template = "bubba rocks";
$validators->rules[$key]->name = "bar";
$validators->rules[$key]->template = "bubba rocks";
return $validator;
}
}
}
public function setValidatorRuleName($rulesetName, $ruleType, $name){
$ruleset = $this->getRuleset($rulesetName);
$validators = $ruleset->validator;
foreach ($validators->rules as $key=>$validator){
if (get_class($validator) === 'Respect\Validation\Rules\\'.$ruleType){
$validators->rules[$key]->name = $name;
return $validator;
}
}
}
public function setValidatorRuleTemplate($rulesetName, $ruleType, $template){
$ruleset = $this->getRuleset($rulesetName);
$validators = $ruleset->validator;
foreach ($validators->rules as $key=>$validator){
if (get_class($validator) === 'Respect\Validation\Rules\\'.$ruleType){
$validators->rules[$key]->template = $template;
return $validator;
}
}
}
}
then I modifed the script and ran it
<?php
set_include_path(implode(PATH_SEPARATOR, array(
realpath('../library'),
realpath(__DIR__)
)));
function __autoload($class_name) {
include $class_name . '.php';
}
use BubbaValidator as v;
try {
$chained = new BubbaValidator();
$chained->key('foo',
v::length(20)->setName('bar')->setTemplate('Custom length message.')
->alnum()->setName('baz')->setTemplate('Custom alnum message.')
);
$chained->setValidatorRuleName('foo', 'Alnum', 'baz');
$chained->setValidatorRuleTemplate('foo', 'Alnum', 'Bubba\'s Custom Alnum!');
$chained->setValidatorRuleName('foo', 'Length', 'bar');
$chained->setValidatorRuleTemplate('foo', 'Length', 'Bubba\'s Custom Length!');
$chained->assert(array(
'foo' => 'Hello, world!',
));
} catch (Respect\Validation\Exceptions\ValidationException $exception) {
$errors = $exception->findMessages(array(
'bar',
'baz',
));
var_dump($errors);
}
to finally get this output:
D:\Users\Bubba\git\Validation\bin>php test.php
array(2) {
["bar"]=>
string(22) "Bubba's Custom Length!"
["baz"]=>
string(21) "Custom alnum message." }
That was fun!
When I do the following:
$arUserStuff = array ('name' => 'username', 'email' => 'test#test.com');
$object = (object) $arUserStuff;
print_r($object);
The print function returns me the following:
stdClass Object ( [name] => username [email] => test#test.com )
How can I change std class object in let's say's User Object?
Create that class, then create an object of it:
class User {
public $name, $email; // public for this example, or set these by constructor
public function __construct( array $fields) {
foreach( $fields as $field => $value)
$this->$field = $value;
}
}
$object = new User;
$object->name = 'username';
$object->email = 'test#test.com';
Or, you can do:
$arUserStuff = array ('name' => 'username', 'email' => 'test#test.com');
$object = new User( $arUserStuff);
Now, from print_r( $object);, you'll get something like this:
User Object ( [name] => username [email] => test#test.com )
actually to do what you want, you should make it like:
$arUserStuff = new ArrayObject(
array (
'name' => 'username', 'email' => 'test#test.com'
)
);
to change the class name you need to create a new class.
It's a rather complex process but you can learn about it here:
http://php.net/manual/en/language.oop5.php
Here's a generic function that converts an array into any type of object, assuming the fields are public
class User { public $name, $email; }
class Dog { public $name, $breed; }
function objFromArray($className, $arr) {
$obj = new $className;
foreach(array_keys(get_class_vars($className)) as $key) {
if (array_key_exists($key, $arr)) {
$obj->$key = $arr[$key];
}
}
return $obj;
}
print_r(objFromArray('User',
array ('name' => 'username', 'email' => 'test#test.com')));
echo "<br/>";
print_r(objFromArray('Dog',
array ('name' => 'Bailey', 'breed' => 'Poodle')));
Output
User Object ( [name] => username [email] => test#test.com )
Dog Object ( [name] => Bailey [breed] => Poodle )
I wanted to make a trait out of it but don't have PHP 5.4 installed to test it. This wouldn't require the fields to be public
trait ConvertibleFromArray {
public static function fromArray($arr) {
var $cls = get_called_class();
var $obj = new $cls;
foreach($arr as $key=>$value) {
if (property_exists($obj, $arr)) {
$obj->$key = $value;
}
}
return $obj;
}
}
class User {
use ConvertibleFromArray;
public $name, $email;
}
class Dog {
use ConvertibleFromArray;
public $name, $breed;
}
print_r(User::fromArray(array ('name' => 'username', 'email' => 'test#test.com')));
print_r(Dog::fromArray(array('name' => 'Bailey', 'breed' => 'Poodle')));
?>
I have a url http://*.com/branch/module/view/id/1/cat/2/etc/3.
It becomes.
array
(
'module'=>'branch',
'controller'=>'module',
'action'=>'view'
);
next I need to get the params.
Ihave this array.
/*function getNextSegments($n,$segments) {
return array_slice ( $q = $this->segments, $n + 1 );
}
$params = getNextSegments(3);
*/
array ( 0 => 'id', 1 => '1', 2 => 'cat', 3 => '2', 4 => 'etc', 5 => '3' );//params
And i wanna convert it to this one:
array
(
'id'=>1,
'cat'=>2,
'etc'=>3,
);
How i can do this using php function. I know I can do using for or foreach, but I think php has such function , but i cant find it :(.
Thank you.
class A {
protected function combine($params) {
$count = count ( $params );
$returnArray = array ();
for($i = 0; $i < $count; $i += 2) {
$g = $i % 2;
if ($g == 0 or $g > 0) {
if (isset ( $params [$i] ) and isset ( $params [$i + 1] ))
$returnArray [$params [$i]] = $params [$i + 1];
}
}
return $returnArray;
}
}
This works normaly. If anybody has better logic for this please help.
Thank you again.
PHP does not have a built-in function for this. I would just implement it with explode and a loop, shouldn't be that hard.
I use a function like this in my extended Zend_Controller_Action class.
public function getCleanParams()
{
$removeElements = array(
'module' => '',
'controller' => '',
'action' => '',
'_dc' => ''
);
return array_diff_key($this->_request->getParams(), $removeElements);
}
That will give you your parameters in a clean fashion and the format you want it in.
You can start building your router with the following regular expression (name the file index.php):
<?php
$pattern = '#^(?P<module>branch)/'.
'(?P<controller>module)/'.
'(?P<action>view)'.
'(:?/id[s]?/(?P<id>[0-9]+))?'.
'(:?/cat[s]?/(?P<cat>[0-9]+))?'.
'(:?/etc[s]?/(?P<etc>[0-9]+))?#ui';
preg_match($pattern, trim($_SERVER['REQUEST_URI'], '/'), $segment);
echo sprintf('<pre>%s</pre>', var_export($segment, true));
Assuming you have PHP 5.4.x installed, you can type the following on the command-line:
% php -S localhost:8765
Now browse to http://localhost:8765/branch/module/view/id/1/cat/2/etc/3
The output will be (removed numeric keys except 0 for clarity):
array (
0 => 'branch/module/view/id/1/cat/2/etc/3',
'module' => 'branch',
'controller' => 'module',
'action' => 'view',
'id' => '1',
'cat' => '2',
'etc' => '3',
)