Have recently been using Symfony2 after using ZF for some time.
I am having problems trying to do something relatively simple, I think.
The following code is within a controller:
private $current_setid = "";
public function __construct() {
$current_set = $this->getCurrentSet();
if ($current_set == "") {
return $this->redirect($this->generateUrl('selectset'));
}
$this->current_setid = $current_set;
}
public function getCurrentSet() {
$session = $this->get("session");
$set = $session->get('set');
return $set;
}
public function setCurrentSet($setid) {
$session = $this->get("session");
$session->set('set', "$setid");
}
If I use __construct() I get errors like:
Fatal error: Call to a member function get() on a non-object in
I have tried using __init() and init() both of which do not seem to get called.
Can anyone point me in the right direction? Is there a simple way to do this or do I have to look into event listeners?
Have you tried getting your session like they do in official documentation?
$session = $this->getRequest()->getSession();
$foo = $session->get('foo');
Basically get fetch dependencies from container and container in the Controller is injected using setter dependency injection. You just not have container in the time of __construct yet.
Just ended up opting for placing a check in every method in the class. Seems silly to have to do that but I find I often have to do that in Symfony2 with the lack of init, postDispatch type methods like ZF has.
Even trying to remove the check to another method was counter productive as I still had to check the return from that method as $this->redirect does not seem to work unless it is within an Action method. For example:
public function isSetSet() {
$current_set = $this->getCurrentSet();
if ($current_set == "") {
$url = $this->generateUrl('selectset');
return $this->redirect($url);
}
return TRUE;
}
public function someAction() {
$check = $this->isSetSet();
if($check != TRUE){
return $check;
}
...
}
So each method needs that 4 line check but the whole check can be done in 4 lines anyway so no need for that extra method:
public function anotherAction() {
$current_setid = $this->getCurrentSet();
if ($current_setid == "") {
return $this->redirect($this->generateUrl('selectset'));
}
...
}
Related
I have a test that fails due to me being unable to successfully stub the get method of the Controller:
1) Tests\my-project\BackendBundle\Service\PdfGenerateServiceTest::test_getHttpPathToPtImage_should_return_the_default_avatar_when_photo_is_null
TypeError: Argument 1 passed to Mock_Pdf_d0288d34::setLogger() must implement interface Psr\Log\LoggerInterface, null given, called in /var/www/my-project/src/my-project/BackendBundle/Service/PdfGenerateService.php on line 66
The test
public function test_getHttpPathToPtImage_should_return_the_default_avatar_when_photo_is_null()
{
$protocolAndHost = "http://foo.bar.com";
$service = new PdfGenerateService($this->createFileServiceMock(), $this->createTranslatorMock(), $this->createSnappyMock(), $this->createContainerMock(), $protocolAndHost);
$httpPathToPtImage = $service->getHttpPathToPtImage(null);
self::assertEquals($httpPathToPtImage, $protocolAndHost . "abc/def");
}
The constructor that is failing
public function __construct(FileService $fileService, Translator $translator, Pdf $snappy, ContainerInterface $container, string $protocolAndHost)
{
$this->fileService = $fileService;
$this->translator = $translator;
$this->currentLocale = $this->translator->getLocale();
/* Could reconfigure the service using `service.yml` to pass these in using DI */
$this->twig = $container->get('twig');
$this->logger = $container->get('logger'); // <--- should not be null
$timeoutInSeconds = 15; // can be high, since the job is done async in a job (user does not wait)
$snappy->setLogger($this->logger); // <--- THIS FAILS due to $this->logger being null
The stubs
protected function createContainerMock()
{
$containerMock = $this->createMock('Symfony\Component\DependencyInjection\ContainerInterface');
$loggerMock = $this->createLoggerMock();
$containerMock->method('get')->will($this->returnValueMap([
['logger', $loggerMock]
]));
return $containerMock;
}
I do not really understand why the the get('logger') call just returns null when I have setup a mock to be returned using the returnValueMap call above.
By chance, I just found a SO question on this topic where someone mentioned that you need to supply all parameters, even optional ones. I then checked out the interface, which does indeed list a second parameter:
public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE);
Still, changing the map to ['logger', null, $loggerMock] made no change, so I am a bit at a loss as to what to try next.
Phpunit 6.5, PHP 7.2, Symfony 3.4
You were really close to the solution. When providing a value for an optional parameter in returnValueMap, you must use the value itself, not just null.
So instead of
['logger', null, $loggerMock]
try specifying
['logger', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $loggerMock]
Complete call looks like this:
$containerMock->method('get')->will($this->returnValueMap([
['logger', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $loggerMock]
]));
We have a Silverstripe 4 project which acts as a headless CMS returning a group of complex data models formatted as JSON.
Here's an example of the code:
class APIController extends ContentController
{
public function index(HTTPRequest $request)
{
$dataArray['model1'] = AccessPointController::getModel1();
$dataArray['model2'] = AccessPointController::getModel2();
$dataArray['model3'] = AccessPointController::getModel3();
$dataArray['model4'] = AccessPointController::getModel4();
$dataArray['model5'] = AccessPointController::getModel5();
$dataArray['model6'] = AccessPointController::getModel6();
$this->response->addHeader('Content-Type', 'application/json');
$this->response->addHeader('Access-Control-Allow-Origin', '*');
return json_encode($dataArray);
}
The problem we're having is the data models have got so complex the generation time for the JSON is running into seconds.
The JSON should only change when site content has been updates so ideally we'd like to cache the JSON & rather than dynamically generating it for each call.
What is the best way to cache the JSON in the above example?
Have you looked at the silverstripe docs about caching?
They do provide a programmatic way to store things in cache. And configuration options what back-ends are to be used to store the cache.
A simple example might be:
I've extended the cache live time here, but still you should note that this cache is not intended for storing generated static content, but rather to reduce load. Your application will still have to compute the api response every 86400 seconds (24 hours).
# app/_config/apiCache.yml
---
Name: apicacheconfig
---
# [... rest of your config config ...]
SilverStripe\Core\Injector\Injector:
Psr\SimpleCache\CacheInterface.apiResponseCache:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "apiResponseCache"
defaultLifetime: 86400
<?php // app/src/FooController.php
class FooController extends \SilverStripe\Control\Controller {
public function getCache() {
return Injector::inst()->get('Psr\SimpleCache\CacheInterface.apiResponseCache');
// or your own cache (see below):
// return new MyCache();
}
protected function hasDataBeenChanged() {
// alternative to this method, you could also simply include Page::get()->max('LastEdited') or whatever in your cache key inside index(), but if you are using your own cache system, you need to handle deleting of old unused cache files. If you are using SilverStripe's cache, it will do that for you
$c = $this->getCache();
$lastCacheTime = $c->has('cacheTime') ? (int)$c->get('cacheTime') : 0;
$lastDataChangeTime = strtotime(Page::get()->max('LastEdited'));
return $lastDataChangeTime > $lastCacheTime;
}
public function index() {
$c = $this->getCache();
$cacheKey = 'indexActionResponse';
if ($c->has($cacheKey) && !$this->hasDataBeenChanged()) {
$data = $c->get($cacheKey);
} else {
$dataArray['model1'] = AccessPointController::getModel1();
$dataArray['model2'] = AccessPointController::getModel2();
$dataArray['model3'] = AccessPointController::getModel3();
$dataArray['model4'] = AccessPointController::getModel4();
$dataArray['model5'] = AccessPointController::getModel5();
$dataArray['model6'] = AccessPointController::getModel6();
$data = json_encode($dataArray);
$c->set($cacheKey, $data);
$c->set('cacheTime', time());
}
$this->response->addHeader('Content-Type', 'application/json');
$this->response->addHeader('Access-Control-Allow-Origin', '*');
return json_encode($dataArray);
}
}
If you are looking for a permanent/persistent cache, that will only ever update when data changed, I suggest you look for a different back-end or just implement a simple cache yourself and use that instead of the silverstripe cache.
class MyCache {
protected function fileName($key) {
if (strpos($key, '/') !== false || strpos($key, '\\') !== false) {
throw new \Exception("Invalid cache key '$key'");
}
return BASE_PATH . "/api-cache/$key.json";
}
public function get($key) {
if ($this->has($key)) {
return file_get_contents($this->fileName($key));
}
return null;
}
public function set($key, $val) {
file_put_contents($this->fileName($key), $val);
}
public function has($key) {
$f = $this->fileName($key);
return #file_exists($f);
}
I'd like to make wrapper to implement simple data binding pattern -- while some data have been modified all registered handlers are got notified. I have started with this (for js target):
class Main {
public static function main() {
var target = new Some();
var binding = new Bindable(target);
binding.one = 5;
// binding.two = 0.12; // intentionally unset field
binding.three = []; // wrong type
binding.four = 'str'; // no such field in wrapped class
trace(binding.one, binding.two, binding.three, binding.four, binding.five);
// outputs: 5, null, [], str, null
trace(target.one, target.two, target.three);
// outputs: 5, null, []
}
}
class Some {
public var one:Int;
public var two:Float;
public var three:Bool;
public function new() {}
}
abstract Bindable<TClass>(TClass) {
public inline function new(source) { this = source; }
#:op(a.b) public function setField<T>(name:String, value:T) {
Reflect.setField(this, name, value);
// TODO notify handlers
return value;
}
#:op(a.b) public function getField<T>(name:String):T {
return cast Reflect.field(this, name);
}
}
So I have some frustrating issues: interface of wrapped object doesn't expose to wrapper, so there's no auto completion or strict type checking, some necessary attributes can be easily omitted or even misspelled.
Is it possible to fix my solution or should I better move to the macros?
I almost suggested here to open an issue regarding this problem. Because some time ago, there was a #:followWithAbstracts meta available for abstracts, which could be (or maybe was?) used to forward fields and call #:op(a.b) at the same time. But that's not really necessary, Haxe is powerful enough already.
abstract Binding<TClass>(TClass) {
public function new(source:TClass) { this = source; }
#:op(a.b) public function setField<T>(name:String, value:T) {
Reflect.setField(this, name, value);
// TODO notify handlers
trace("set: $name -> $value");
return value;
}
#:op(a.b) public function getField<T>(name:String):T {
trace("get: $name");
return cast Reflect.field(this, name);
}
}
#:forward
#:multiType
abstract Bindable<TClass>(TClass) {
public function new(source:TClass);
#:to function to(t:TClass) return new Binding(t);
}
We use here multiType abstract to forward fields, but resolved type is actually regular abstract. In effect, you have completion working and #:op(a.b) called at the same time.
You need #:forward meta on your abstract. However, this will not make auto-completion working unless you remove #:op(A.B) because it shadows forwarded fields.
EDIT: it seems that shadowing happened first time I added #:forward to your abstract, afterwards auto-completion worked just fine.
I would like to only get records where the active indicator is true:
class Question {
/**
* One Question has one Figure
* #ORM\OneToOne(targetEntity="QuestionFigure", mappedBy="question")
*/
private $figure;
public function getFigure()
{
$criteria = Criteria::create()->where(Criteria::expr()->eq("active", true));
return $this->figure->matching($criteria);
}
When I do this, I get the error:
Attempted to call an undefined method named "matching" of class
I believe this is because the matching method can only be applied to an ArrayCollection which $this->figure is not. What would be a similar way of achieving this same result?
Edit based on answer provided by Ihor Kostrov:
getActive() is returning nothing. Testing this out, this works:
public function getFigure()
{
if (!empty($this->figure) && $this->figure->getId() === 1) {
return $this->figure;
}
return null;
}
But changing the id to 2 does not work ($this->figure->getId() === 2). I am thinking this is because of the one-to-one relationship doctrine only fetches one row?
Yes, you have OneToOne, so you cat try this
public function getFigure(): ?QuestionFigure
{
if (!empty($this->figure) && $this->figure->getActive()) {
return $this->figure;
}
return null;
}
No! You must not have logic in your entity!
To have all question with have QuestionFigure active you must configure a repository and you implement a method.
I need to return the value from my Responder object. Right now, I have:
private function pro():int {
gateway.connect('http://10.0.2.2:5000/gateway');
var id:int = 0;
function ret_pr(result:*):int {
return result
}
var responder:Responder = new Responder(ret_pr);
gateway.call('sx.xj', responder);
return id
}
Basically, I need to know how to get the return value of ret_pr into id or anything that I return from that function. The responder just seems to eat it. I can't use a public variable somewhere else because this will be running multiple times at once, so I need local scope.
This is how I'd write a connection to the AMF server, call it and store the resulting value. Remember that the result won't be available instantly so you'll set up the responder to "respond" to the data once it returns from the server.
public function init():void
{
connection = new NetConnection();
connection.connect('http://10.0.2.2:5000/gateway');
setSessionID( 1 );
}
public function setSessionID(user_id:String):void
{
var amfResponder:Responder = new Responder(setSessionIDResult, onFault);
connection.call("ServerService.setSessionID", amfResponder , user_id);
}
private function setSessionIDResult(result:Object):void {
id = result.id;
// here you'd do something to notify that the data has been downloaded. I'll usually
// use a custom Event class that just notifies that the data is ready,but I'd store
// it here in the class with the AMF call to keep all my data in one place.
}
private function onFault(fault:Object):void {
trace("AMFPHP error: "+fault);
}
I hope that can point you in the right direction.
private function pro():int {
gateway.connect('http://10.0.2.2:5000/gateway');
var id:int = 0;
function ret_pr(result:*):int {
return result
}
var responder:Responder = new Responder(ret_pr);
gateway.call('sx.xj', responder);
return id
}
This code is never going to get you what you want. You need to use a proper result function. The anonymous function responder return value will not be used by the surrounding function. It will always return 0 in this case. You are dealing with an asynchronous call here, and your logic needs to handle that accordingly.
private function pro():void {
gateway.connect('http://10.0.2.2:5000/gateway');
var responder:Responder = new Responder(handleResponse);
gateway.call('sx.xj', responder);
}
private function handleResponse(result:*):void
{
var event:MyCustomNotificationEvent = new MyCustomNotificationEvent(
MyCustomNotificationEvent.RESULTS_RECEIVED, result);
dispatchEvent(event);
//a listener responds to this and does work on your result
//or maybe here you add the result to an array, or some other
//mechanism
}
The point there being using anon functions/closures isn't going to give you some sort of pseudo-syncronous behavior.