Jump to content

kicken

Gurus
  • Posts

    4,695
  • Joined

  • Last visited

  • Days Won

    177

Everything posted by kicken

  1. The change you need to apply is to change that line so it's setting that variable up as an array rather than a string. $V0f14082c['parser-options']=""; changes to $V0f14082c['parser-options']=array();
  2. Yes, as indicated on the home page. The code is over on Github if your interested. Only the basics if you stick to what the library can natively do given it's options. The more JS you know the more fancy/custom stuff you can do with it, but for basic chart rendering you only really need to include the library and write: new Chart(document.getElementById('where'), jsonConfiguration); Here's something to make it even easier if you stick to the basics: window.addEventListener('DOMContentLoaded', function(){ var chartElements = document.querySelectorAll('div[data-chart]'); chartElements.forEach(function(div){ var configuration = JSON.parse(div.dataset.chart); var canvas = document.createElement('canvas'); div.appendChild(canvas); new Chart(canvas, configuration); }); }); That will look for any div tags with a data-chart attribute and initialize a chart in that element using the configuration in the value of the data attribute. Not really. For the most part you can just generate the configuration in PHP as an array and json_encode it. So you first would generate your configuration as a PHP array using the data from your database: $configuration = [ 'type' => 'bar', 'data' => [ 'labels' => ['January', 'February', 'March', 'April', 'May', 'June', 'July'], 'datasets' => [ [ 'label' => 'Group A', 'backgroundColor' => '#84999a', 'borderColor' => '#ff0000', 'borderWidth' => 1, //your values from the DB go here 'data' => [81072, 14498, 20460, 14651, 34036, 20056, 27270] ] ] ], 'options' => [ 'responsive' => true, 'legend' => [ 'position' => 'top' ], 'title' => [ 'display' => true, 'text' => 'Income' ] ] ]; Then, if your using the function above to initialize the charts just attach it to a div tag in a data-chart attribute using json_encode (and htmlspecialchars for protection). <div data-chart="<?=htmlspecialchars(json_encode($configuration));?>"></div>
  3. More or less. A reverse DNS entry (IP to Name) is not required so it may or may not exist for any given IP. If it does exist, the IP owner can make it whatever they want it to be. For the majority of IPs the reverse entry is controlled by the host or ISP. An ISP will generally either not have a reverse entry or it will be something dynamically generated. A hosting service will generally be similar to an ISP, but some do allow the end-user to control the reverse entry if they have a dedicated IP. With the ubiquity of shared hosting these days though the reverse generally just is something generic rather than mapping to a domain similar to a forward lookup mapping to an ip.
  4. If you do decide to step things up a level I highly suggest just finding a good library to do your chart rendering. Chart.js is my tool of choice currently. It offloads the chart drawing to the client so all you have to do on the server side is generate a json structure.
  5. You're going to have to spend time trying to debug it or contact the author. There's no obvious error that can be seen looking at the site. It makes the request to send messages just fine. You need to figure out what's going on after that and why it's either not saving the sent messages or not delivering them to the users.
  6. Rule #3 is about avoiding something like this: function blah($list){ foreach ($list as $a){ if ($a['flag']){ foreach ($a['list'] as $b){ if ($b['flag']){ //something } else if ($b['flag2']){ foreach ($b['list'] as $c){ if ($c['x']){ //something } else { //other thing } } } } } } } It's hard to read, particularly when the //something's are longer than one line and it wastes horizontal space. It'd be better to pull chunks out into other functions, for example: function blah($list){ foreach ($list as $a){ if ($a['flag']){ processFlagList($a); } } } function processFlaglist($list){ foreach ($list as $b){ if ($b['flag']){ //something } else if ($b['flag2']){ processFlag2($b); } } } function processFlag2List($list){ foreach ($b['list'] as $c){ if ($c['x']){ //something } else { //other thing } } } Much easier to follow, especially if you only want a high-level overview of the code and don't need the details. This can be valuable in some big if/else chain like described above. Imagine you were just quickly trying to get an idea of what some function does and you see this: function blah($list){ foreach ($list as $a){ if ($a['flag1']){ //something 20 lines long } else if ($a['flag2']){ //something else 20 lines long } else if ($a['flag3']){ //yet another thing 30 lines long } else { //and finally the last thing 8 lines long } } } Remember, imagine all those //something's are big blocks of code that prevent you from seeing the whole tree like you can here. That'd be a whole lot of code to read through, figure out what it does and keep track of it all. What if instead you saw this: function blah($list){ foreach ($list as $a){ if ($a['flag1']){ sendToProcessor($a); } else if ($a['flag2']){ markCompleted($a); } else if ($a['flag3']){ notifyTheCEO($a); } else { setEverythingOnFire(); } } } Much easier and the names of the function let you know what's happening without having to dig deeper. If you decide you need to know exactly how the code sets everything on fire you can easily* go find out, but if you don't care then that code isn't cluttering up your view. * With a good IDE going and finding out what a function does is easy. In PHPStorm for example I could just CTRL+Click on (or CTRL+B with the text cursor in) the function name and it'll take me to it. Without a good IDE it might be harder.
  7. The beginning would be when you click the submit button to start the upload. You can use Javascript to make your loading GIF visible at that point, it will remain visible while the browser upload the files to the server. Once the upload is complete and your script returns a response then it'll replace the current page and loading gif. document.getElementById('upload-form').addEventListener('submit', function(){ document.getElementById('loading-gif').style.display='block'; }); <img id="loading-gif" src="loading.gif" style="display:none;"> <form id="upload-form"> </form>
  8. That's far too restrictive and either something that was misunderstood or came from a crazy person. I came across a rule of thumb some time ago that I try to live by which goes a little something like this: A function should fit entirely on screen (no vertical scrolling required). How many lines this is will vary based on resolution/fonts and such, but ends up being around 65 lines for my environment. A single line shouldn't be longer than the width of the screen (no horizontal scrolling required). That ends up being around 120 characters for me. A function shouldn't have statements nested more than 5 levels deep Use a descriptive name for the function even if it's verbose. As with many things those are rules of thumb and not everything will conform to them, but I find they are generally pretty easy to follow. If you end up breaking one of them, then it's time to analyze the situation and decide if you need to break the rule or if it can be refactored. For rule #1, some things I only consider to be one line because they can be easily code-folded away. For example I have many functions that are way more than 65 lines, but a lot of those lines are a large SQL statement that I can just fold down to a single line. Rule #3 helps make rule #2 easier to hit as you don't waste a bunch of screen real estate indenting things a bunch. Something nested 5 levels with a 4-space indent would be wasting 20 characters on the indentation. In my experience, refactoring old code to help fit the above rules is relatively simple. Start by just moving blocks of code, for example move the body of a loop into a new function then call that function from the loop. Alternatively, just move the entire loop and replace it with a function call. Having a bunch of functions in a single file isn't necessarily bad. It'd be best to at least group them in some way rather than just dump everything in one file. For example, I have a few functions that deal with manipulating arrays which are all grouped together in a file. Some other functions that handle validating input are in a separate file. If you want to do one function, one file like with classes that's fine as well but not generally necessary imo.
  9. I'm guessing the gap you're referring to is the upload process. When you upload files PHP handles receiving the incoming data and saving it into temporary files on the server. That all happens internally before your code runs. Only after that upload process is complete does your script run and then have an opportunity to do something with the uploaded files. The files should be uploaded in the order of your file input elements. If you're using a single input element with the multiple attribute then I don't believe there is anyway to control the order. Not that any of that should matter anyway. By the time your code can do anything all the files will be uploaded and available so you can just do what you will with them.
  10. For reference, the example code I posted in your other thread was able to crawl all 127 pages of my site in about 20 seconds.
  11. Not very. If you want to go more OOP then you'd break things out into classes a bit more and get rid of your global variables. Each class would be responsible for some portion of the overall task and you then combine them together to accomplish the task. I can see at least three potential classes. Crawler which is responsible for downloading the URLs that are in the queue. LinkExtractor which is responsible for finding links in the downloaded document. LinkQueue which is responsible for tracking which links need to be downloaded. Linked above are some re-writes of your code in more of an OOP style. If you have any specific questions, feel free to ask. For the most part I just re-arranged your code, but your createLink function (now LinkExtractor::resolveUrl) needs some work to actually resolve a relative URL properly. I fixed it up a little, but it's still far from perfect. Once you get a basic version working, what you'd do is update your Crawler class so it can download several URLs in parallel using something like the curl_multi_* functions or library such as guzzle. Don't worry about this at all until you have your crawler working as you want it first though. Debugging a sequential process is much easier than a parallel process. Ultimately though crawling a site is something that is going to take time. If you want to have a service where users can submit a URL to crawl then you'd want to have the crawling task done by a background worker and notify the user when it's done either by sending them to a page that can monitor the progress or by sending them an email with a link containing the results.
  12. If your post body is json then it seems you need to either use getContent to grab the json and parse it manually or use toArray to grab then parsed array. $request->request->get() is how you would get normal post parameters but it seems that it doesn't process json documents and make the keys available via it.
  13. If you do a typical multipart/form-data post then certainly you can send both. It'd be just like submitting a normal HTML form with a file input. Most your frameworks and such should probably handle this just fine. If you want to do a two-part request where part 1 is just a JSON document and part 2 is the file content then you certain can do that, but you may not find built-in support for it in your frameworks because as far as I know it's not something that's usually done. As such, you'd have to spend time creating your own code to handle such a request in whatever way you need to. As far as I know the more common way things are done is to use separate requests so you'd first POST your metadata then PUT the file content to s certain URL. For example if you were adding a document to some project X you might POST /api/project/X/documents to create a new document and the response of that would give you a document ID number Y. Then you'd PUT /api/project/X/documents/Y/content with the binary content of the file. So I guess the question mostly is do you want to spend the time writing the code to make your nice-looking single-request or uglify your request to take advantage of existing code.
  14. Sports? 🤷‍♂️ I don't really follow football (or much of any sport for that matter) so meh. I'll be half-watching the game with friends but have no stake in it. You?
  15. Can one user be both an owner of one project and a vendor of a different project? If so, it'd seem like a single user class with a quality access control system would be the best way to handle things. For example, rather than give ROLE_OWNER directly to the user give it to a ($user,$project) pair your code you can do something like $project->isOwner($user) to check (perhaps with a voter to integrate it into Symfony's default access control system). My Symfony system doesn't allow student's to login so there's only one kind of user. That decade old application does have separate student/staff users which if converted directly to a Symfony setup would result in two distinct classes. If I were going to move the system over I'd probably combine them as at the login/user account level there's really not much difference between the two. The things that are different I'd probably try and factor out into something separate. I'd have to spend a lot more time thinking about and maybe experiment some on that decision though.
  16. Just a guess but what if you don't have your repositories extend AbstractUserRepository and instead just be their own separate repositories?
  17. Hard to really say, it's just an ever ongoing process really. I still wouldn't consider myself any kind of expert. I'd say I just have an average knowledge of how it works and how to use it. I also don't use it a lot either. I have a few smaller sites that use it and work on them occasionally but probably at least 80% of my time is spent maintaining/improving the legacy code in a decade old website. I'd say it was probably at least six months or so, maybe a year before I'd say I had a decent grasp on how to actually work with Symfony instead of against it. For example, the forms component was annoying when I first started because I had no idea how to use it and be able to control how the forms were rendered so I frequently just wrote the form HTML myself and processed the data by grabbing it from the Request object just like how I would in in the old site using $_GET/$_POST. Every time I needed a form though I'd try and do it the Symfony way first and eventually I figured it out and started converting all the old forms. And only in the last year or so have I gotten to the point where I fell like I'm really taking advantage of using services and dependency injection by defining my controllers as services and moving more of my logic out of the controllers and into other service classes. As far as resources go there's nothing in particular. Just google and the source code for the most part. Whenever I get stuck or want to do something I am unsure of I start dissecting the source code and/or search google for help. The official documentation is ok and generally worth looking through but I find it too shallow and doesn't go into enough detail for things. It's hard to navigate also, even if I know something is in the official documentation (ie, form control constraints) it's far easier to find it via google than to go to the Symfony site and try to find it. Analyzing the source is a great way to get things figured out, even if it's not the fastest. It has the additional benefit of demonstrating good design solutions as well. At lot of what I've learned over the years hasn't really been Symfony specific but more general design principals and how to apply them successfully. I've been applying a lot of this to that decade old app as I revamp old features. For example, newer features get implemented in a dependency injection friendly way and the logic is more confined to dedicated and re-usable service classes rather than in each individual page file. My goal is to eventually morph the website into something more Symfony like and maybe eventually migrate it entirely. It'll depend mostly on how you store your users and their associated roles. For example, if you wanted you could actually make separate classes like you suggested and just have each class can override the getRoles() method to return the appropriate roles. In my case, I have a single UserAccount class with a property called dynamicRoles which is a list of UserRole objects that define which roles a user has. Then the UserAccount::getRoles() method iterates over that list and gets the actual role names and returns them. UserAccount.orm.yml AppBundle\Entity\UserAccount: type: entity ... oneToMany: dynamicRoles: targetEntity: UserRole mappedBy: user UserAccount::getRoles() /** * @return string[] */ public function getRoles(){ $roleStrings = $this->dynamicRoles->map(function(UserRole $userRole){ return $userRole->getRole(); })->toArray(); return $roleStrings; } Finding which users have a given role then is a matter of querying the DB for the UserRole objects that have the given role then getting the associated user: $userList = $em->getRepository(UserRole::class)->findBy([ 'role' => 'ROLE_RUN_OFFICIAL' ]); $userList = array_map(function(UserRole $role){ return $role->getUser(); }, $userList); I think that the above could be simplified to remove the UserRole object and just store an array of strings directly on the UserAccount object instead, but that kind of echos back to the previous point of things being an ever on-going learning process. When I originally wrote this I didn't know as much and needed that extra object. I would guess you probably wouldn't need to be checking your roles much as you'd likely just configure the different routes to only be accessible by specific roles. I just mentioned the methods so you'd know about them in case you did need them. I mostly just use them to show/hide various buttons/links based on what roles a user has on a few select pages. For an JSON API I don't imagine you'd have much use for them as you'd just control access to the endpoints in your security configuration but now you know if you do need them. ** My apps are all Symfony 3/4 based, I've no idea if anything of the above has changed in newer versions. Eventually I'll get mine updated and learn what's new.
  18. Sounds like Roles is what you want. Define various roles that represent the actions a user can take and check for those in your code. Then you can create meta roles that are combinations of those actions and assign those to the users. For example, I have a small application to handle student transcripts. Any user that can login can lookup/view students and add notes. Other limited actions include importing/editing student/course data, running official/unofficial transcript reports, and managing users. For that I have a role setup such as: role_hierarchy: ROLE_RUN_UNOFFICIAL: - 'ROLE_USER' ROLE_RUN_OFFICIAL: - 'ROLE_RUN_UNOFFICIAL' ROLE_MANAGE_USERS: - 'ROLE_USER' ROLE_MANAGE_STUDENTS: - 'ROLE_USER' ROLE_DEVELOPER: - 'ROLE_USER' - 'ROLE_RUN_OFFICIAL' - 'ROLE_RUN_UNOFFICIAL' - 'ROLE_MANAGE_USERS' - 'ROLE_MANAGE_STUDENTS' - 'ROLE_MANAGE_COURSES' ROLE_MANAGE_COURSES: - 'ROLE_USER' ROLE_USER is applied to every user and is your basic "is a user" role and can search/view student data. ROLE_RUN_UNOFFICIAL applies to users who can run unofficial transcript reports. Add ROLE_USER below it means that anyone who has this role also has ROLE_USER ROLE_RUN_OFFICIAL applies to users can run official reports. Being able to run official reports can also run unofficial reports so they inherit that role as well (and by extension, ROLE_USER). ROLE_DEVELOPER is a special role for myself and a few others that basically opens up everything. Once you setup your role hierarchy as you need it then you can use them either in your security configuration as needed to limit access or check in your code using the isGranted controller method. if ($officialButton->isClicked()){ if (!$this->isGranted('ROLE_RUN_OFFICIAL')){ throw $this->createAccessDeniedException(); } return $this->processTranscriptRequest(Mode::OFFICIAL, $data); } In your templates, you can use the is_granted test to conditionally show things: <td> {% if is_granted('ROLE_RUN_OFFICIAL') %} {{ form_widget(form.official, { 'attr': { 'class': 'button' } }) }} {% endif %} </td>
  19. That constructor syntax is new to PHP 8. It lets you declare the class properties as constructor arguments and reduces the amount of boiler plate code. I might warm up to it eventually but at the moment I'm not a big fan and will probably stick to the traditional syntax for a while. The arguments for $pathItem is also a new PHP 8 feature. This is useful for functions that take a lot of parameters as you can specify just the ones you care about and leave the rest to their default values. In the past you'd generally either pass a single array with keys to achieve the same thing but that meant giving up support for things like IDE autocomplete, type checking, etc.
  20. No. Constructors only run when you're creating an instance of an object using new. Static methods are not associated with an instance so there's no need to run a constructor. You also cannot use $this or any non-static class properties or methods
  21. composer install will install whatever the composer.lock file says to install. If there is no composer.lock file then it behaves like composer update does. composer update will parse the composer.json file and calculate all the dependencies based on the given requirements and current operating environment. Once it determines what versions to install and verified the platform requirements it will write the final configuration to composer.lock so that it's quick and easy to install all those dependencies again if needed. So if you move your project, including the composer.lock file, to a different platform and just run composer install then you could end up with compatibility issues because it might install the wrong dependencies for the platform. The reason for doing things this way rather than just always resolving dependencies is so you can install a known-good set of dependencies. If you just got the latest versions every time then 6 months from now when you want to install your application on a new server you might get an update version of some library that ends up not working rather than the working version you last tested with. Guzzle/promises for example recently released a new minor version that has a bug which broke one of my hobby projects. Had it been a real project being deployed somewhere it would have been very bad to have something like that happen. It's different in that you don't have to keep separate composer.phar files around. You can just have one that is kept up to date but run it with the appropriate PHP version. If you'd rather just have separate composer.phar files that's fine too.
  22. What's important is that your development environment match your production environment, at least to the minor level. If you do something like develop with PHP 7.4 but production runs PHP 7.3 then you might have issues. The reason is that when you update your packages with composer it will calculate the dependencies based on the running PHP version (7.4 in the example) then save those dependencies to the composer.lock file. When you then run composer install on production it will try and install the locked 7.4-based dependencies and potentially cause issues. If you have different projects using different versions of PHP then you'll have to have those different versions of PHP available for your development environment as well and make sure you use the correct version when working on a project. You don't (or at least shouldn't) need separate composer installs, just make sure you run it with the correct PHP version for that project. A simple wrapper script for each project could be used to help with this. If you don't want to deal with multiple PHP versions then an alternative solution is to fake your production platform using composer's config.platform option in your composer.json file: { "config": { "platform": { "php": "7.3.0" } } } That tells composer to calculate the dependencies while assuming that the current PHP version is 7.3.0 rather than looking at what's actually running. Whenever you update your production PHP version you'll need to update the version here to match.
  23. Some sort of version control is worth the effort to learn. Git isn't too bad, and is where most things are going these days. There's others available though you could try. I still make heavy use of Subversion and it's fairly easy to use on windows with a tool like TortoiseSVN.
  24. In general and by default https is not required to use sessions. However, your host may have set session.cookie_secure to on in their PHP configuration which would make it so that https is required.
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.